Update to use LOG_FORMAT environment variable
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
# Change log
|
# Change log
|
||||||
|
|
||||||
## master
|
## master
|
||||||
|
* Container's stdout can now be set to JSON format (set LOG_FORMAT=json)
|
||||||
* MQ error logs (in JSON or plain text) are now mirrored on stdout for the container.
|
* MQ error logs (in JSON or plain text) are now mirrored on stdout for the container.
|
||||||
* `chkmqready` now waits until MQSC scripts in `/etc/mqm` have been applied
|
* `chkmqready` now waits until MQSC scripts in `/etc/mqm` have been applied
|
||||||
* `chkmqready` and `chkmqhealthy` now run as the "mqm" user
|
* `chkmqready` and `chkmqhealthy` now run as the "mqm" user
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ RUN go build ./cmd/runmqserver/
|
|||||||
RUN go build ./cmd/chkmqready/
|
RUN go build ./cmd/chkmqready/
|
||||||
RUN go build ./cmd/chkmqhealthy/
|
RUN go build ./cmd/chkmqhealthy/
|
||||||
# Run all unit tests
|
# Run all unit tests
|
||||||
RUN go test -v ./cmd/...
|
RUN go test -v ./cmd/runmqserver/
|
||||||
|
RUN go test -v ./cmd/chkmqready/
|
||||||
|
RUN go test -v ./cmd/chkmqhealthy/
|
||||||
RUN go test -v ./internal/...
|
RUN go test -v ./internal/...
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -63,6 +65,6 @@ RUN chmod ug+x /usr/local/bin/runmqserver \
|
|||||||
# Always use port 1414
|
# Always use port 1414
|
||||||
EXPOSE 1414
|
EXPOSE 1414
|
||||||
|
|
||||||
ENV LANG=en_US.UTF-8 AMQ_DIAGNOSTIC_MSG_SEVERITY=1 AMQ_ADDITIONAL_JSON_LOG=1
|
ENV LANG=en_US.UTF-8 AMQ_DIAGNOSTIC_MSG_SEVERITY=1 AMQ_ADDITIONAL_JSON_LOG=1 LOG_FORMAT=simple
|
||||||
|
|
||||||
ENTRYPOINT ["runmqserver"]
|
ENTRYPOINT ["runmqserver"]
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ In order to use the image, it is necessary to accept the terms of the IBM MQ lic
|
|||||||
* **LICENSE** - Set this to `accept` to agree to the MQ Advanced for Developers license. If you wish to see the license you can set this to `view`.
|
* **LICENSE** - Set this to `accept` to agree to the MQ Advanced for Developers license. If you wish to see the license you can set this to `view`.
|
||||||
* **LANG** - Set this to the language you would like the license to be printed in.
|
* **LANG** - Set this to the language you would like the license to be printed in.
|
||||||
* **MQ_QMGR_NAME** - Set this to the name you want your Queue Manager to be created with.
|
* **MQ_QMGR_NAME** - Set this to the name you want your Queue Manager to be created with.
|
||||||
|
* **LOG_FORMAT** - Set this to change the format of the logs which are printed on the container's stdout. Set to "json" to use JSON format (JSON object per line); set to "simple" to use a simple human-readable. Defaults to "simple".
|
||||||
|
|
||||||
|
|
||||||
# Issues and contributions
|
# Issues and contributions
|
||||||
|
|||||||
@@ -74,23 +74,15 @@ func logTermination(args ...interface{}) {
|
|||||||
log.Error(msg)
|
log.Error(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonLogs() bool {
|
func getLogFormat() string {
|
||||||
e := os.Getenv("MQ_ALPHA_JSON_LOGS")
|
return os.Getenv("LOG_FORMAT")
|
||||||
if e == "true" || e == "1" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func mirrorToStdout(msg string) {
|
|
||||||
fmt.Println(msg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatSimple(datetime string, message string) string {
|
func formatSimple(datetime string, message string) string {
|
||||||
return fmt.Sprintf("%v %v\n", datetime, message)
|
return fmt.Sprintf("%v %v\n", datetime, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func mirrorLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart bool) (chan error, error) {
|
func mirrorLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart bool, mf mirrorFunc) (chan error, error) {
|
||||||
// Always use the JSON log as the source
|
// Always use the JSON log as the source
|
||||||
// Put the queue manager name in quotes to handle cases like name=..
|
// Put the queue manager name in quotes to handle cases like name=..
|
||||||
qm, err := mqini.GetQueueManager(name)
|
qm, err := mqini.GetQueueManager(name)
|
||||||
@@ -99,15 +91,7 @@ func mirrorLogs(ctx context.Context, wg *sync.WaitGroup, name string, fromStart
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json")
|
f := filepath.Join(mqini.GetErrorLogDirectory(qm), "AMQERR01.json")
|
||||||
if jsonLogs() {
|
return mirrorLog(ctx, wg, f, fromStart, mf)
|
||||||
return mirrorLog(ctx, wg, f, fromStart, mirrorToStdout)
|
|
||||||
}
|
|
||||||
return mirrorLog(ctx, wg, f, fromStart, func(msg string) {
|
|
||||||
// Parse the JSON message, and print a simplified version
|
|
||||||
var obj map[string]interface{}
|
|
||||||
json.Unmarshal([]byte(msg), &obj)
|
|
||||||
fmt.Printf(formatSimple(obj["ibm_datetime"].(string), obj["message"].(string)))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureDebugLogger() {
|
func configureDebugLogger() {
|
||||||
@@ -119,8 +103,12 @@ func configureDebugLogger() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureLogger() {
|
func configureLogger() (mirrorFunc, error) {
|
||||||
if jsonLogs() {
|
// Set the simple formatter by default
|
||||||
|
log.SetFormatter(new(simpleTextFormatter))
|
||||||
|
f := getLogFormat()
|
||||||
|
switch f {
|
||||||
|
case "json":
|
||||||
formatter := logrus.JSONFormatter{
|
formatter := logrus.JSONFormatter{
|
||||||
FieldMap: logrus.FieldMap{
|
FieldMap: logrus.FieldMap{
|
||||||
logrus.FieldKeyMsg: "message",
|
logrus.FieldKeyMsg: "message",
|
||||||
@@ -130,7 +118,17 @@ func configureLogger() {
|
|||||||
TimestampFormat: timestampFormat,
|
TimestampFormat: timestampFormat,
|
||||||
}
|
}
|
||||||
logrus.SetFormatter(&formatter)
|
logrus.SetFormatter(&formatter)
|
||||||
} else {
|
return func(msg string) {
|
||||||
log.SetFormatter(new(simpleTextFormatter))
|
fmt.Println(msg)
|
||||||
|
}, nil
|
||||||
|
case "simple":
|
||||||
|
return func(msg string) {
|
||||||
|
// Parse the JSON message, and print a simplified version
|
||||||
|
var obj map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(msg), &obj)
|
||||||
|
fmt.Printf(formatSimple(obj["ibm_datetime"].(string), obj["message"].(string)))
|
||||||
|
}, nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid value for LOG_FORMAT: %v", f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,9 +30,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func doMain() error {
|
func doMain() error {
|
||||||
configureLogger()
|
mf, err := configureLogger()
|
||||||
|
if err != nil {
|
||||||
|
logTermination(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
configureDebugLogger()
|
configureDebugLogger()
|
||||||
err := ready.Clear()
|
err = ready.Clear()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logTermination(err)
|
logTermination(err)
|
||||||
return err
|
return err
|
||||||
@@ -83,7 +87,7 @@ func doMain() error {
|
|||||||
cancelMirror()
|
cancelMirror()
|
||||||
}()
|
}()
|
||||||
// TODO: Use the error channel
|
// TODO: Use the error channel
|
||||||
_, err = mirrorLogs(ctx, &wg, name, newQM)
|
_, err = mirrorLogs(ctx, &wg, name, newQM, mf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logTermination(err)
|
logTermination(err)
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ func TestLicenseNotSet(t *testing.T) {
|
|||||||
if rc != 1 {
|
if rc != 1 {
|
||||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||||
}
|
}
|
||||||
|
expectTerminationMessage(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLicenseView(t *testing.T) {
|
func TestLicenseView(t *testing.T) {
|
||||||
@@ -222,6 +223,7 @@ func TestCreateQueueManagerFail(t *testing.T) {
|
|||||||
if rc != 1 {
|
if rc != 1 {
|
||||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||||
}
|
}
|
||||||
|
expectTerminationMessage(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestStartQueueManagerFail causes a failure of `strmqm`
|
// TestStartQueueManagerFail causes a failure of `strmqm`
|
||||||
@@ -234,7 +236,7 @@ func TestStartQueueManagerFail(t *testing.T) {
|
|||||||
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName())
|
img, _, err := cli.ImageInspectWithRaw(context.Background(), imageName())
|
||||||
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ")
|
oldEntrypoint := strings.Join(img.Config.Entrypoint, " ")
|
||||||
containerConfig := container.Config{
|
containerConfig := container.Config{
|
||||||
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1"},
|
Env: []string{"LICENSE=accept", "MQ_QMGR_NAME=qm1", "DEBUG=1"},
|
||||||
// Override the entrypoint to replace `strmqm` with a script which deletes the queue manager.
|
// Override the entrypoint to replace `strmqm` with a script which deletes the queue manager.
|
||||||
// This will cause `strmqm` to return with an exit code of 72.
|
// This will cause `strmqm` to return with an exit code of 72.
|
||||||
Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm && exec " + oldEntrypoint},
|
Entrypoint: []string{"bash", "-c", "echo '#!/bin/bash\ndltmqm $@ && strmqm $@' > /opt/mqm/bin/strmqm && exec " + oldEntrypoint},
|
||||||
@@ -245,10 +247,7 @@ func TestStartQueueManagerFail(t *testing.T) {
|
|||||||
if rc != 1 {
|
if rc != 1 {
|
||||||
t.Errorf("Expected rc=1, got rc=%v", rc)
|
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||||
}
|
}
|
||||||
m := terminationMessage(t)
|
expectTerminationMessage(t)
|
||||||
if m == "" {
|
|
||||||
t.Error("Expected termination message to be set")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestVolumeUnmount runs a queue manager with a volume, and then forces an
|
// TestVolumeUnmount runs a queue manager with a volume, and then forces an
|
||||||
@@ -453,7 +452,7 @@ func TestErrorLogRotation(t *testing.T) {
|
|||||||
"LICENSE=accept",
|
"LICENSE=accept",
|
||||||
"MQ_QMGR_NAME=" + qmName,
|
"MQ_QMGR_NAME=" + qmName,
|
||||||
"MQMAXERRORLOGSIZE=65536",
|
"MQMAXERRORLOGSIZE=65536",
|
||||||
"MQ_ALPHA_JSON_LOGS=true",
|
"LOG_FORMAT=json",
|
||||||
},
|
},
|
||||||
ExposedPorts: nat.PortSet{
|
ExposedPorts: nat.PortSet{
|
||||||
"1414/tcp": struct{}{},
|
"1414/tcp": struct{}{},
|
||||||
@@ -505,7 +504,7 @@ func TestErrorLogRotation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestJSONLogs(t *testing.T) {
|
func TestJSONLogFormat(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
cli, err := client.NewEnvClient()
|
cli, err := client.NewEnvClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -514,8 +513,7 @@ func TestJSONLogs(t *testing.T) {
|
|||||||
containerConfig := container.Config{
|
containerConfig := container.Config{
|
||||||
Env: []string{
|
Env: []string{
|
||||||
"LICENSE=accept",
|
"LICENSE=accept",
|
||||||
"MQ_QMGR_NAME=qm1",
|
"LOG_FORMAT=json",
|
||||||
"MQ_ALPHA_JSON_LOGS=1",
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
id := runContainer(t, cli, &containerConfig)
|
id := runContainer(t, cli, &containerConfig)
|
||||||
@@ -537,6 +535,27 @@ func TestJSONLogs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBadLogFormat(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cli, err := client.NewEnvClient()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
containerConfig := container.Config{
|
||||||
|
Env: []string{
|
||||||
|
"LICENSE=accept",
|
||||||
|
"LOG_FORMAT=fake",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
id := runContainer(t, cli, &containerConfig)
|
||||||
|
defer cleanContainer(t, cli, id)
|
||||||
|
rc := waitForContainer(t, cli, id, 5)
|
||||||
|
if rc != 1 {
|
||||||
|
t.Errorf("Expected rc=1, got rc=%v", rc)
|
||||||
|
}
|
||||||
|
expectTerminationMessage(t)
|
||||||
|
}
|
||||||
|
|
||||||
// TestMQJSONDisabled tests the case where MQ's JSON logging feature is
|
// TestMQJSONDisabled tests the case where MQ's JSON logging feature is
|
||||||
// specifically disabled (which will disable log mirroring)
|
// specifically disabled (which will disable log mirroring)
|
||||||
func TestMQJSONDisabled(t *testing.T) {
|
func TestMQJSONDisabled(t *testing.T) {
|
||||||
|
|||||||
@@ -102,6 +102,13 @@ func terminationMessage(t *testing.T) string {
|
|||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func expectTerminationMessage(t *testing.T) {
|
||||||
|
m := terminationMessage(t)
|
||||||
|
if m == "" {
|
||||||
|
t.Error("Expected termination message to be set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
func cleanContainer(t *testing.T, cli *client.Client, ID string) {
|
||||||
i, err := cli.ContainerInspect(context.Background(), ID)
|
i, err := cli.ContainerInspect(context.Background(), ID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user