Repository: apache/camel-k
Pull request #2252 (2021-04-30 12:10:03)

Added support for the json log format via the logging trait
- includes support for pretty printing the json


<!-- Description -->




<!--
Enter your extended release note in the below block. If the PR requires
additional action from users switching to the new release, include the string
"action required". If no release note is required, write "NONE". 

You can (optionally) mark this PR with labels "kind/bug" or "kind/feature" to make sure
the text is added to the right section of the release notes. 
-->

**Release Note**
```release-note
Added support for the json log format 
```

orpiske: How does it look:

Json:
![camel-k-json-logs](https://user-images.githubusercontent.com/1851560/116693162-cd1bdb00-a9bd-11eb-9506-3cca15a3a84f.png)

Json with pretty-print:
![camel-k-json-pretty-print](https://user-images.githubusercontent.com/1851560/116693209-dc9b2400-a9bd-11eb-951e-413a2878eee0.png)



Patch:
diff --git a/deploy/traits.yaml b/deploy/traits.yaml
index 378601f21..000289e95 100755
--- a/deploy/traits.yaml
+++ b/deploy/traits.yaml
@@ -576,6 +576,12 @@ traits:
   - name: color
     type: bool
     description: Colorize the log output
+  - name: json
+    type: bool
+    description: Output the log in json format
+  - name: json-pretty-print
+    type: bool
+    description: Enable "pretty printing" of the json log
 - name: master
   platform: false
   profiles:
diff --git a/docs/modules/traits/pages/logging.adoc b/docs/modules/traits/pages/logging.adoc
index d2f979bdf..a8981743c 100755
--- a/docs/modules/traits/pages/logging.adoc
+++ b/docs/modules/traits/pages/logging.adoc
@@ -29,6 +29,14 @@ The following configuration options are available:
 | bool
 | Colorize the log output
 
+| logging.json
+| bool
+| Output the log in json format
+
+| logging.json-pretty-print
+| bool
+| Enable "pretty printing" of the json log
+
 |===
 
 // End of autogenerated code - DO NOT EDIT! (configuration)
diff --git a/pkg/apis/camel/v1alpha1/error_handler_types.go b/pkg/apis/camel/v1alpha1/error_handler_types.go
index 5019796ad..d60074a99 100644
--- a/pkg/apis/camel/v1alpha1/error_handler_types.go
+++ b/pkg/apis/camel/v1alpha1/error_handler_types.go
@@ -48,6 +48,8 @@ type BeanProperties struct {
 	v1.RawMessage `json:",omitempty"`
 }
 
+// +kubebuilder:object:generate=false
+
 // ErrorHandler is a generic interface that represent any type of error handler specification
 type ErrorHandler interface {
 	Type() ErrorHandlerType
diff --git a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
index 71938f5dd..dd0e4ba6a 100644
--- a/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1alpha1/zz_generated.deepcopy.go
@@ -26,6 +26,26 @@ func (in *AuthorizationSpec) DeepCopy() *AuthorizationSpec {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *BeanProperties) DeepCopyInto(out *BeanProperties) {
+	*out = *in
+	if in.RawMessage != nil {
+		in, out := &in.RawMessage, &out.RawMessage
+		*out = make(v1.RawMessage, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BeanProperties.
+func (in *BeanProperties) DeepCopy() *BeanProperties {
+	if in == nil {
+		return nil
+	}
+	out := new(BeanProperties)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *Endpoint) DeepCopyInto(out *Endpoint) {
 	*out = *in
@@ -83,6 +103,151 @@ func (in *EndpointProperties) DeepCopy() *EndpointProperties {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ErrorHandlerBean) DeepCopyInto(out *ErrorHandlerBean) {
+	*out = *in
+	out.ErrorHandlerNone = in.ErrorHandlerNone
+	if in.BeanType != nil {
+		in, out := &in.BeanType, &out.BeanType
+		*out = new(string)
+		**out = **in
+	}
+	if in.BeanProperties != nil {
+		in, out := &in.BeanProperties, &out.BeanProperties
+		*out = new(BeanProperties)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorHandlerBean.
+func (in *ErrorHandlerBean) DeepCopy() *ErrorHandlerBean {
+	if in == nil {
+		return nil
+	}
+	out := new(ErrorHandlerBean)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ErrorHandlerDeadLetterChannel) DeepCopyInto(out *ErrorHandlerDeadLetterChannel) {
+	*out = *in
+	in.ErrorHandlerLog.DeepCopyInto(&out.ErrorHandlerLog)
+	if in.DLCEndpoint != nil {
+		in, out := &in.DLCEndpoint, &out.DLCEndpoint
+		*out = new(Endpoint)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorHandlerDeadLetterChannel.
+func (in *ErrorHandlerDeadLetterChannel) DeepCopy() *ErrorHandlerDeadLetterChannel {
+	if in == nil {
+		return nil
+	}
+	out := new(ErrorHandlerDeadLetterChannel)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ErrorHandlerLog) DeepCopyInto(out *ErrorHandlerLog) {
+	*out = *in
+	out.ErrorHandlerNone = in.ErrorHandlerNone
+	if in.Parameters != nil {
+		in, out := &in.Parameters, &out.Parameters
+		*out = new(ErrorHandlerParameters)
+		(*in).DeepCopyInto(*out)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorHandlerLog.
+func (in *ErrorHandlerLog) DeepCopy() *ErrorHandlerLog {
+	if in == nil {
+		return nil
+	}
+	out := new(ErrorHandlerLog)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ErrorHandlerNone) DeepCopyInto(out *ErrorHandlerNone) {
+	*out = *in
+	out.baseErrorHandler = in.baseErrorHandler
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorHandlerNone.
+func (in *ErrorHandlerNone) DeepCopy() *ErrorHandlerNone {
+	if in == nil {
+		return nil
+	}
+	out := new(ErrorHandlerNone)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ErrorHandlerParameters) DeepCopyInto(out *ErrorHandlerParameters) {
+	*out = *in
+	if in.RawMessage != nil {
+		in, out := &in.RawMessage, &out.RawMessage
+		*out = make(v1.RawMessage, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorHandlerParameters.
+func (in *ErrorHandlerParameters) DeepCopy() *ErrorHandlerParameters {
+	if in == nil {
+		return nil
+	}
+	out := new(ErrorHandlerParameters)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ErrorHandlerRef) DeepCopyInto(out *ErrorHandlerRef) {
+	*out = *in
+	out.baseErrorHandler = in.baseErrorHandler
+	if in.RawMessage != nil {
+		in, out := &in.RawMessage, &out.RawMessage
+		*out = make(v1.RawMessage, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorHandlerRef.
+func (in *ErrorHandlerRef) DeepCopy() *ErrorHandlerRef {
+	if in == nil {
+		return nil
+	}
+	out := new(ErrorHandlerRef)
+	in.DeepCopyInto(out)
+	return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ErrorHandlerSpec) DeepCopyInto(out *ErrorHandlerSpec) {
+	*out = *in
+	if in.RawMessage != nil {
+		in, out := &in.RawMessage, &out.RawMessage
+		*out = make(v1.RawMessage, len(*in))
+		copy(*out, *in)
+	}
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrorHandlerSpec.
+func (in *ErrorHandlerSpec) DeepCopy() *ErrorHandlerSpec {
+	if in == nil {
+		return nil
+	}
+	out := new(ErrorHandlerSpec)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *EventTypeSpec) DeepCopyInto(out *EventTypeSpec) {
 	*out = *in
@@ -370,6 +535,7 @@ func (in *KameletBindingSpec) DeepCopyInto(out *KameletBindingSpec) {
 	}
 	in.Source.DeepCopyInto(&out.Source)
 	in.Sink.DeepCopyInto(&out.Sink)
+	in.ErrorHandler.DeepCopyInto(&out.ErrorHandler)
 	if in.Steps != nil {
 		in, out := &in.Steps, &out.Steps
 		*out = make([]Endpoint, len(*in))
diff --git a/pkg/trait/logging.go b/pkg/trait/logging.go
index 7c1776a27..84b111780 100644
--- a/pkg/trait/logging.go
+++ b/pkg/trait/logging.go
@@ -25,7 +25,10 @@ import (
 )
 
 const (
-	envVarQuarkusLogConsoleColor = "QUARKUS_LOG_CONSOLE_COLOR"
+	envVarQuarkusLogConsoleColor           = "QUARKUS_LOG_CONSOLE_COLOR"
+	envVarQuarkusLogConsoleJson            = "QUARKUS_LOG_CONSOLE_JSON"
+	envVarQuarkusLogConsoleJsonPrettyPrint = "QUARKUS_LOG_CONSOLE_JSON_PRETTY_PRINT"
+	depQuarkusLoggingJson                  = "mvn:io.quarkus:quarkus-logging-json"
 )
 
 // This trait is used to control logging options (such as color)
@@ -35,12 +38,18 @@ type loggingTrait struct {
 	BaseTrait `property:",squash"`
 	// Colorize the log output
 	Color *bool `property:"color" json:"color,omitempty"`
+	// Output the log in json format
+	Json *bool `property:"json" json:"json,omitempty"`
+	// Enable "pretty printing" of the json log
+	JsonPrettyPrint *bool `property:"json-pretty-print" json:"jsonPrettyPrint,omitempty"`
 }
 
 func newLoggingTraitTrait() Trait {
 	return &loggingTrait{
-		BaseTrait: NewBaseTrait("logging", 800),
-		Color:     util.BoolP(true),
+		BaseTrait:       NewBaseTrait("logging", 800),
+		Color:           util.BoolP(true),
+		Json:            util.BoolP(false),
+		JsonPrettyPrint: util.BoolP(false),
 	}
 }
 
@@ -49,11 +58,30 @@ func (l loggingTrait) Configure(environment *Environment) (bool, error) {
 		return false, nil
 	}
 
-	return environment.IntegrationInPhase(v1.IntegrationPhaseDeploying, v1.IntegrationPhaseRunning), nil
+	return environment.IntegrationInPhase(v1.IntegrationPhaseInitialization, v1.IntegrationPhaseDeploying,
+		v1.IntegrationPhaseRunning), nil
 }
 
 func (l loggingTrait) Apply(environment *Environment) error {
-	envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleColor, strconv.FormatBool(*l.Color))
+
+	if environment.IntegrationInPhase(v1.IntegrationPhaseInitialization) {
+		if *l.Json {
+			if environment.Integration.Status.Dependencies == nil {
+				environment.Integration.Status.Dependencies = make([]string, 0)
+			}
+
+			util.StringSliceUniqueAdd(&environment.Integration.Status.Dependencies, depQuarkusLoggingJson)
+		}
+
+		return nil
+	}
+
+	envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleJson, strconv.FormatBool(*l.Json))
+	envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleJsonPrettyPrint, strconv.FormatBool(*l.JsonPrettyPrint))
+
+	if !*l.Json {
+		envvar.SetVal(&environment.EnvVars, envVarQuarkusLogConsoleColor, strconv.FormatBool(*l.Color))
+	}
 
 	return nil
 }


Test patch:
diff --git a/pkg/trait/logging_test.go b/pkg/trait/logging_test.go
index 020d65ada..c8696d471 100644
--- a/pkg/trait/logging_test.go
+++ b/pkg/trait/logging_test.go
@@ -22,13 +22,14 @@ import (
 	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 	"github.com/apache/camel-k/pkg/util/camel"
 	"github.com/apache/camel-k/pkg/util/kubernetes"
+	"github.com/apache/camel-k/pkg/util/test"
 	"github.com/stretchr/testify/assert"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"testing"
 )
 
-func createLoggingTestEnv(t *testing.T) *Environment {
+func createLoggingTestEnv(t *testing.T, color bool, json bool, jsonPrettyPrint bool) *Environment {
 	c, err := camel.DefaultCatalog()
 	if err != nil {
 		panic(err)
@@ -48,6 +49,13 @@ func createLoggingTestEnv(t *testing.T) *Environment {
 			},
 			Spec: v1.IntegrationSpec{
 				Profile: v1.TraitProfileOpenShift,
+				Traits: map[string]v1.TraitSpec{
+					"logging": test.TraitSpecFromMap(t, map[string]interface{}{
+						"color":             color,
+						"json":              json,
+						"json-pretty-print": jsonPrettyPrint,
+					}),
+				},
 			},
 		},
 		IntegrationKit: &v1.IntegrationKit{
@@ -71,18 +79,24 @@ func createLoggingTestEnv(t *testing.T) *Environment {
 	return res
 }
 
+func createDefaultLoggingTestEnv(t *testing.T) *Environment {
+	return createLoggingTestEnv(t, true, false, false)
+}
+
 func NewLoggingTestCatalog() *Catalog {
 	return NewCatalog(context.TODO(), nil)
 }
 
 func TestEmptyLoggingTrait(t *testing.T) {
-	env := createLoggingTestEnv(t)
+	env := createDefaultLoggingTestEnv(t)
 	err := NewLoggingTestCatalog().apply(env)
 
 	assert.Nil(t, err)
 	assert.NotEmpty(t, env.ExecutedTraits)
 
 	quarkusConsoleColor := false
+	jsonFormat := false
+	jsonPrettyPrint := false
 
 	for _, e := range env.EnvVars {
 		if e.Name == envVarQuarkusLogConsoleColor {
@@ -90,7 +104,59 @@ func TestEmptyLoggingTrait(t *testing.T) {
 				quarkusConsoleColor = true
 			}
 		}
+
+		if e.Name == envVarQuarkusLogConsoleJson {
+			if e.Value == "true" {
+				jsonFormat = true
+			}
+		}
+
+		if e.Name == envVarQuarkusLogConsoleJsonPrettyPrint {
+			if e.Value == "true" {
+				jsonPrettyPrint = true
+			}
+		}
 	}
 
 	assert.True(t, quarkusConsoleColor)
+	assert.False(t, jsonFormat)
+	assert.False(t, jsonPrettyPrint)
+	assert.NotEmpty(t, env.ExecutedTraits)
+}
+
+func TestJsonLoggingTrait(t *testing.T) {
+	env := createLoggingTestEnv(t, true, true, false)
+	err := NewLoggingTestCatalog().apply(env)
+
+	assert.Nil(t, err)
+	assert.NotEmpty(t, env.ExecutedTraits)
+
+	quarkusConsoleColor := false
+	jsonFormat := true
+	jsonPrettyPrint := false
+
+	for _, e := range env.EnvVars {
+		if e.Name == envVarQuarkusLogConsoleColor {
+			if e.Value == "true" {
+				quarkusConsoleColor = true
+			}
+		}
+
+		if e.Name == envVarQuarkusLogConsoleJson {
+			if e.Value == "true" {
+				jsonFormat = true
+			}
+		}
+
+		if e.Name == envVarQuarkusLogConsoleJsonPrettyPrint {
+			if e.Value == "true" {
+				jsonPrettyPrint = true
+			}
+		}
+	}
+
+	assert.False(t, quarkusConsoleColor)
+	assert.True(t, jsonFormat)
+	assert.False(t, jsonPrettyPrint)
+	assert.NotEmpty(t, env.ExecutedTraits)
 }