From b4b775681a9dc9f9bcd93ab3ca51b6f74057d064 Mon Sep 17 00:00:00 2001 From: Kevin Franklin Kim Date: Wed, 8 Oct 2025 15:43:32 +0200 Subject: [PATCH] feat: switch to semconv --- keeltest/servicehttp.go | 3 +- log/fields.go | 8 ++-- log/fields_code.go | 14 +++++-- log/fields_duration.go | 23 ++++++++---- log/fields_http.go | 61 +++++++++++++++--------------- log/fields_keel.go | 11 ------ log/fields_messaging.go | 48 ++++++++++++++++++------ log/fields_net.go | 10 +++-- log/fields_service.go | 45 ++++++++++------------ log/fields_stream.go | 6 ++- log/logger.go | 5 ++- log/otel.go | 48 ++++++++++++++++++++++++ log/with.go | 64 +++++++++++++++----------------- net/http/middleware/logger.go | 5 ++- net/http/roundtripware/logger.go | 9 +++-- net/stream/jetstream/stream.go | 9 ++++- semconv/http.go | 22 +++++++++++ semconv/keel.go | 29 +++++++++++++++ semconv/tracking.go | 15 ++++++++ service/goroutine.go | 7 ++-- service/http.go | 8 ++-- service/httphealthz.go | 3 +- telemetry/{ctx.go => context.go} | 8 ++-- telemetry/resource.go | 2 +- utils/net/http/errors.go | 3 +- 25 files changed, 310 insertions(+), 156 deletions(-) delete mode 100644 log/fields_keel.go create mode 100644 log/otel.go create mode 100644 semconv/http.go create mode 100644 semconv/keel.go create mode 100644 semconv/tracking.go rename telemetry/{ctx.go => context.go} (100%) diff --git a/keeltest/servicehttp.go b/keeltest/servicehttp.go index ff1647b..cf7a2fb 100644 --- a/keeltest/servicehttp.go +++ b/keeltest/servicehttp.go @@ -7,6 +7,7 @@ import ( "net/http/httptest" "strings" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" "github.com/foomo/keel/log" @@ -58,7 +59,7 @@ func (s *ServiceHTTP) Start(ctx context.Context) error { ip = "0.0.0.0" } - fields = append(fields, log.FNetHostIP(ip), log.FNetHostPort(port)) + fields = append(fields, log.Attributes(semconv.ServerAddress(ip), semconv.ServerPortKey.String(port))...) } s.l.Info("starting http test service", fields...) diff --git a/log/fields.go b/log/fields.go index 2a8a63f..51f3353 100644 --- a/log/fields.go +++ b/log/fields.go @@ -18,22 +18,22 @@ const ( JSONKey = "json" ) -// FNum - returns zap field +// FNum creates a zap.Field with a given number under the key "num". func FNum(num int) zap.Field { return zap.Int(NumKey, num) } -// FName - returns zap field +// FName creates a zap.Field with a given string under the key "name". func FName(name string) zap.Field { return zap.String(NameKey, name) } -// FValue - returns zap field +// FValue creates a zap.Field with a given value under the key "value". func FValue(value interface{}) zap.Field { return zap.String(ValueKey, fmt.Sprintf("%v", value)) } -// FJSON - returns zap field +// FJSON creates a zap.Field with a given value under the key "json". func FJSON(v interface{}) zap.Field { if out, err := json.Marshal(v); err != nil { return zap.String(JSONKey+"_error", err.Error()) diff --git a/log/fields_code.go b/log/fields_code.go index 53ad3db..43ed976 100644 --- a/log/fields_code.go +++ b/log/fields_code.go @@ -5,24 +5,32 @@ import ( ) const ( + // Deprecated: use semconv messaging attributes instead. CodeInstanceKey = "code_instance" - CodePackageKey = "code_package" - CodeMethodKey = "code_method" - CodeLineKey = "code_line" + // Deprecated: use semconv messaging attributes instead. + CodePackageKey = "code_package" + // Deprecated: use semconv messaging attributes instead. + CodeMethodKey = "code_method" + // Deprecated: use semconv messaging attributes instead. + CodeLineKey = "code_line" ) +// Deprecated: use semconv messaging attributes instead. func FCodeInstance(v string) zap.Field { return zap.String(CodeInstanceKey, v) } +// Deprecated: use semconv messaging attributes instead. func FCodePackage(v string) zap.Field { return zap.String(CodePackageKey, v) } +// Deprecated: use semconv messaging attributes instead. func FCodeMethod(v string) zap.Field { return zap.String(CodeMethodKey, v) } +// Deprecated: use semconv messaging attributes instead. func FCodeLine(v int) zap.Field { return zap.Int(CodeLineKey, v) } diff --git a/log/fields_duration.go b/log/fields_duration.go index 1a2691d..5f7e3a8 100644 --- a/log/fields_duration.go +++ b/log/fields_duration.go @@ -7,28 +7,37 @@ import ( ) const ( - DurationKey = "duration" - DurationSecKey = "duration_sec" - DurationMinKey = "duration_min" + // DurationKey - generic duration attribute + DurationKey = "duration" + // DurationSecKey - duration in seconds + DurationSecKey = "duration_sec" + // DurationMinKey - duration in minutes + DurationMinKey = "duration_min" + // DurationHourKey - duration in hours DurationHourKey = "duration_hour" ) +// FDuration creates a zap.Field with a given time.Duration converted to milliseconds under the key "duration". func FDuration(duration time.Duration) zap.Field { - return zap.Float64(DurationKey, float64(duration)/float64(time.Millisecond)) + return zap.Int64(DurationKey, duration.Milliseconds()) } +// FDurationSec creates a zap.Field with a given time.Duration converted to seconds under the key "duration_sec". func FDurationSec(duration time.Duration) zap.Field { - return zap.Float64(DurationSecKey, float64(duration)/float64(time.Second)) + return zap.Float64(DurationSecKey, duration.Seconds()) } +// FDurationMin creates a zap.Field with a given time.Duration converted to minutes under the key "duration_min". func FDurationMin(duration time.Duration) zap.Field { - return zap.Float64(DurationMinKey, float64(duration)/float64(time.Minute)) + return zap.Float64(DurationMinKey, duration.Minutes()) } +// FDurationHour creates a zap.Field with a given time.Duration converted to hours under the key "duration_hour". func FDurationHour(duration time.Duration) zap.Field { - return zap.Float64(DurationHourKey, float64(duration)/float64(time.Hour)) + return zap.Float64(DurationHourKey, duration.Hours()) } +// FDurationFn returns a function that returns a zap.Field with a given time.Duration converted to milliseconds under the key "duration". func FDurationFn() func() zap.Field { start := time.Now() diff --git a/log/fields_http.go b/log/fields_http.go index d49704b..c92f852 100644 --- a/log/fields_http.go +++ b/log/fields_http.go @@ -5,110 +5,109 @@ import ( ) const ( - // HTTPServerNameKey represents the name of the service handling the request + // Deprecated: use semconv messaging attributes instead. HTTPServerNameKey = "http_server_name" - - // HTTPMethodKey represents the HTTP request method. + // Deprecated: use semconv messaging attributes instead. HTTPMethodKey = "http_method" - - // HTTPTargetKey represents the full request target as passed in a HTTP - // request line or equivalent, e.g. "/path/12314/?q=ddds#123". + // Deprecated: use semconv messaging attributes instead. HTTPTargetKey = "http_target" - - // HTTPHostKey represents the value of the HTTP host header. + // Deprecated: use semconv messaging attributes instead. HTTPHostKey = "http_host" - - // HTTPStatusCodeKey represents the HTTP response status code. + // Deprecated: use semconv messaging attributes instead. HTTPStatusCodeKey = "http_status_code" - - // HTTPUserAgentKey represents the Value of the HTTP User-Agent header sent by the client. + // Deprecated: use semconv messaging attributes instead. HTTPUserAgentKey = "http_user_agent" - - // HTTPClientIPKey represents the IP address of the original client behind all proxies, - // if known (e.g. from X-Forwarded-For). + // Deprecated: use semconv messaging attributes instead. HTTPClientIPKey = "http_client_ip" - - // HTTPRequestContentLengthKey represents the size of the request payload body in bytes. + // Deprecated: use semconv messaging attributes instead. HTTPRequestContentLengthKey = "http_read_bytes" - - // HTTPWroteBytesKey represents the size of the response payload body in bytes. + // Deprecated: use semconv messaging attributes instead. HTTPWroteBytesKey = "http_wrote_bytes" // #nosec - - // HTTPSchemeKey represents the URI scheme identifying the used protocol. + // Deprecated: use semconv messaging attributes instead. HTTPSchemeKey = "http_scheme" - - // HTTPFlavorKey represents the Kind of HTTP protocol used. + // Deprecated: use semconv messaging attributes instead. HTTPFlavorKey = "http_flavor" - - // HTTPRequestIDKey represents the HTTP request id if known (e.g. from X-Request-ID). + // Deprecated: use semconv messaging attributes instead. HTTPRequestIDKey = "http_request_id" - - // HTTPSessionIDKey represents the HTTP session id if known (e.g. from X-Session-ID). + // Deprecated: use semconv messaging attributes instead. HTTPSessionIDKey = "http_session_id" - - // HTTPTrackingIDKey represents the HTTP tracking id if known (e.g. from X-Tracking-ID). + // Deprecated: use semconv messaging attributes instead. HTTPTrackingIDKey = "http_tracking_id" - - // HTTPRefererKey identifies the address of the web page (i.e., the URI or IRI), from which the resource has been requested. + // Deprecated: use semconv messaging attributes instead. HTTPRefererKey = "http_referer" ) +// Deprecated: use semconv messaging attributes instead. func FHTTPServerName(id string) zap.Field { return zap.String(HTTPServerNameKey, id) } +// Deprecated: use semconv messaging attributes instead. func FHTTPRequestID(id string) zap.Field { return zap.String(HTTPRequestIDKey, id) } +// Deprecated: use semconv messaging attributes instead. func FHTTPSessionID(id string) zap.Field { return zap.String(HTTPSessionIDKey, id) } +// Deprecated: use semconv messaging attributes instead. func FHTTPTrackingID(id string) zap.Field { return zap.String(HTTPTrackingIDKey, id) } +// Deprecated: use semconv messaging attributes instead. func FHTTPRequestContentLength(bytes int64) zap.Field { return zap.Int64(HTTPRequestContentLengthKey, bytes) } +// Deprecated: use semconv messaging attributes instead. func FHTTPWroteBytes(bytes int64) zap.Field { return zap.Int64(HTTPWroteBytesKey, bytes) } +// Deprecated: use semconv messaging attributes instead. func FHTTPStatusCode(status int) zap.Field { return zap.Int(HTTPStatusCodeKey, status) } +// Deprecated: use semconv messaging attributes instead. func FHTTPTarget(target string) zap.Field { return zap.String(HTTPTargetKey, target) } +// Deprecated: use semconv messaging attributes instead. func FHTTPClientIP(clientIP string) zap.Field { return zap.String(HTTPClientIPKey, clientIP) } +// Deprecated: use semconv messaging attributes instead. func FHTTPFlavor(flavor string) zap.Field { return zap.String(HTTPFlavorKey, flavor) } +// Deprecated: use semconv messaging attributes instead. func FHTTPScheme(scheme string) zap.Field { return zap.String(HTTPSchemeKey, scheme) } +// Deprecated: use semconv messaging attributes instead. func FHTTPUserAgent(userAgent string) zap.Field { return zap.String(HTTPUserAgentKey, userAgent) } +// Deprecated: use semconv messaging attributes instead. func FHTTPReferer(host string) zap.Field { return zap.String(HTTPRefererKey, host) } +// Deprecated: use semconv messaging attributes instead. func FHTTPHost(host string) zap.Field { return zap.String(HTTPHostKey, host) } +// Deprecated: use semconv messaging attributes instead. func FHTTPMethod(name string) zap.Field { return zap.String(HTTPMethodKey, name) } diff --git a/log/fields_keel.go b/log/fields_keel.go deleted file mode 100644 index 81352ee..0000000 --- a/log/fields_keel.go +++ /dev/null @@ -1,11 +0,0 @@ -package log - -import ( - "go.opentelemetry.io/otel/attribute" -) - -const ( - KeelServiceTypeKey = attribute.Key("keel.service.type") - KeelServiceNameKey = attribute.Key("keel.service.name") - KeelServiceInstKey = attribute.Key("keel.service.inst") -) diff --git a/log/fields_messaging.go b/log/fields_messaging.go index ab4b4c9..b5b2acf 100644 --- a/log/fields_messaging.go +++ b/log/fields_messaging.go @@ -1,65 +1,89 @@ package log import ( + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" ) const ( - MessagingSystemKey = "messaging_system" - MessagingDestinationKey = "messaging_destination" - MessagingDestinationKindKey = "messaging_destination_kind" - MessagingProtocolKey = "messaging_protocol" - MessagingProtocolVersionKey = "messaging_protocol_version" - MessagingURLKey = "messaging_url" - MessagingMessageIDKey = "messaging_message_id" - MessagingConversationIDKey = "messaging_conversation_id" - MessagingMessagePayloadSizeBytesKey = "messaging_message_payload_size_bytes" + // Deprecated: use semconv messaging attributes instead. + MessagingSystemKey = "messaging_system" + // Deprecated: use semconv messaging attributes instead. + MessagingDestinationKey = "messaging_destination" + // Deprecated: use semconv messaging attributes instead. + MessagingDestinationKindKey = "messaging_destination_kind" + // Deprecated: use semconv messaging attributes instead. + MessagingProtocolKey = "messaging_protocol" + // Deprecated: use semconv messaging attributes instead. + MessagingProtocolVersionKey = "messaging_protocol_version" + // Deprecated: use semconv messaging attributes instead. + MessagingURLKey = "messaging_url" + // Deprecated: use semconv messaging attributes instead. + MessagingMessageIDKey = "messaging_message_id" + // Deprecated: use semconv messaging attributes instead. + MessagingConversationIDKey = "messaging_conversation_id" + // Deprecated: use semconv messaging attributes instead. + MessagingMessagePayloadSizeBytesKey = "messaging_message_payload_size_bytes" + // Deprecated: use semconv messaging attributes instead. MessagingMessagePayloadCompressedSizeBytesKey = "messaging_message_payload_compressed_size_bytes" ) +// Deprecated: use semconv messaging attributes instead. type MessagingDestinationKind string const ( + // Deprecated: use semconv messaging attributes instead. MessagingDestinationKindQueue MessagingDestinationKind = "queue" + // Deprecated: use semconv messaging attributes instead. MessagingDestinationKindTopic MessagingDestinationKind = "topic" ) +// Deprecated: use semconv messaging attributes instead. func FMessagingSystem(value string) zap.Field { return zap.String(MessagingSystemKey, value) } +// Deprecated: use semconv.MessagingDestinationName instead. func FMessagingDestination(value string) zap.Field { - return zap.String(MessagingDestinationKey, value) + return Attribute(semconv.MessagingDestinationName(value)) } +// Deprecated: use semconv messaging attributes instead. func FMessagingDestinationKind(value MessagingDestinationKind) zap.Field { return zap.String(MessagingDestinationKindKey, string(value)) } +// Deprecated: use semconv messaging attributes instead. func FMessagingProtocol(value string) zap.Field { return zap.String(MessagingProtocolKey, value) } +// Deprecated: use semconv messaging attributes instead. func FMessagingProtocolVersion(value string) zap.Field { return zap.String(MessagingProtocolVersionKey, value) } +// Deprecated: use semconv messaging attributes instead. func FMessagingURL(value string) zap.Field { return zap.String(MessagingURLKey, value) } +// Deprecated: use semconv.MessagingMessageID instead. func FMessagingMessageID(value string) zap.Field { - return zap.String(MessagingMessageIDKey, value) + return Attribute(semconv.MessagingMessageID(value)) } +// Deprecated: use semconv.MessagingMessageConversationID instead. func FMessagingConversationID(value string) zap.Field { - return zap.String(MessagingConversationIDKey, value) + return Attribute(semconv.MessagingMessageConversationID(value)) } +// Deprecated: use semconv messaging attributes instead. func FMessagingMessagePayloadSizeBytes(value string) zap.Field { return zap.String(MessagingMessagePayloadSizeBytesKey, value) } +// Deprecated: use semconv messaging attributes instead. func FMessagingMessagePayloadCompressedSizeBytes(value string) zap.Field { return zap.String(MessagingMessagePayloadCompressedSizeBytesKey, value) } diff --git a/log/fields_net.go b/log/fields_net.go index 73227fc..d1b2f77 100644 --- a/log/fields_net.go +++ b/log/fields_net.go @@ -1,21 +1,23 @@ package log import ( + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" ) const ( - // NetHostIPKey represents the local host IP. Useful in case of a multi-IP host. + // Deprecated: use semconv messaging attributes instead. NetHostIPKey = "net_host_ip" - - // NetHostPortKey represents the local host port. + // Deprecated: use semconv messaging attributes instead. NetHostPortKey = "net_host_port" ) +// Deprecated: use semconv messaging attributes instead. func FNetHostIP(ip string) zap.Field { - return zap.String(NetHostIPKey, ip) + return Attribute(semconv.HostIP(ip)) } +// Deprecated: use semconv messaging attributes instead. func FNetHostPort(port string) zap.Field { return zap.String(NetHostPortKey, port) } diff --git a/log/fields_service.go b/log/fields_service.go index 0496e91..32b423f 100644 --- a/log/fields_service.go +++ b/log/fields_service.go @@ -5,58 +5,53 @@ import ( ) const ( - // PeerServiceKey represents the ServiceNameKey name of the remote service. - // Should equal the actual `service.name` resource attribute of the remote service, if any. + // Deprecated: use semconv messaging attributes instead. PeerServiceKey = "peer_service" + // Deprecated: use semconv messaging attributes instead. + ServiceTypeKey = "service_type" + // Deprecated: use semconv messaging attributes instead. + ServiceNameKey = "service_name" + // Deprecated: use semconv messaging attributes instead. + ServiceMethodKey = "service_method" + // Deprecated: use semconv messaging attributes instead. + ServiceNamespaceKey = "service_namespace" + // Deprecated: use semconv messaging attributes instead. + ServiceInstanceIDKey = "service_instance.id" + // Deprecated: use semconv messaging attributes instead. + ServiceVersionKey = "service_version" ) +// Deprecated: use semconv messaging attributes instead. func FPeerService(name string) zap.Field { return zap.String(PeerServiceKey, name) } -const ( - ServiceTypeKey = "service_type" - - // ServiceNameKey represents the NameKey of the service. - ServiceNameKey = "service_name" - - // ServiceMethodKey represents the Method of the service. - ServiceMethodKey = "service_method" - - // ServiceNamespaceKey represents a namespace for `service.name`. This needs to - // have meaning that helps to distinguish a group of services. For example, the - // team name that owns a group of services. `service.name` is expected to be - // unique within the same namespace. - ServiceNamespaceKey = "service_namespace" - - // ServiceInstanceIDKey represents a unique identifier of the service instance. In conjunction - // with the `service.name` and `service.namespace` this must be unique. - ServiceInstanceIDKey = "service_instance.id" - - // ServiceVersionKey represents the version of the service API. - ServiceVersionKey = "service_version" -) - +// Deprecated: use semconv messaging attributes instead. func FServiceType(name string) zap.Field { return zap.String(ServiceTypeKey, name) } +// Deprecated: use semconv messaging attributes instead. func FServiceName(name string) zap.Field { return zap.String(ServiceNameKey, name) } +// Deprecated: use semconv messaging attributes instead. func FServiceNamespace(namespace string) zap.Field { return zap.String(ServiceNamespaceKey, namespace) } +// Deprecated: use semconv messaging attributes instead. func FServiceInstanceID(id string) zap.Field { return zap.String(ServiceInstanceIDKey, id) } +// Deprecated: use semconv messaging attributes instead. func FServiceVersion(version string) zap.Field { return zap.String(ServiceVersionKey, version) } +// Deprecated: use semconv messaging attributes instead. func FServiceMethod(method string) zap.Field { return zap.String(ServiceMethodKey, method) } diff --git a/log/fields_stream.go b/log/fields_stream.go index 312ff3a..f7ad425 100644 --- a/log/fields_stream.go +++ b/log/fields_stream.go @@ -5,14 +5,18 @@ import ( ) const ( - StreamQueueKey = "queue" + // Deprecated: use semconv messaging attributes instead. + StreamQueueKey = "queue" + // Deprecated: use semconv messaging attributes instead. StreamSubjectKey = "subject" ) +// Deprecated: use semconv messaging attributes instead. func FStreamQueue(queue string) zap.Field { return zap.String(StreamQueueKey, queue) } +// Deprecated: use semconv messaging attributes instead. func FStreamSubject(name string) zap.Field { return zap.String(StreamSubjectKey, name) } diff --git a/log/logger.go b/log/logger.go index 841cfe7..6dcf4d8 100644 --- a/log/logger.go +++ b/log/logger.go @@ -2,6 +2,7 @@ package log import ( "fmt" + "strings" semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" @@ -41,8 +42,8 @@ func NewLogger(level, encoding string) *zap.Logger { config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder } - config.EncoderConfig.CallerKey = string(semconv.CodeFilePathKey) - config.EncoderConfig.StacktraceKey = string(semconv.CodeStacktraceKey) + config.EncoderConfig.CallerKey = strings.ReplaceAll(string(semconv.CodeFilePathKey), ".", "_") + config.EncoderConfig.StacktraceKey = strings.ReplaceAll(string(semconv.CodeStacktraceKey), ".", "_") config.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) { encoder.AppendString(caller.File) } diff --git a/log/otel.go b/log/otel.go new file mode 100644 index 0000000..830dce0 --- /dev/null +++ b/log/otel.go @@ -0,0 +1,48 @@ +package log + +import ( + "strings" + + "go.opentelemetry.io/otel/attribute" + "go.uber.org/zap" +) + +func Attributes(attrs ...attribute.KeyValue) []zap.Field { + ret := make([]zap.Field, len(attrs)) + for i, attr := range attrs { + ret[i] = Attribute(attr) + } + + return ret +} + +func Attribute(attr attribute.KeyValue) zap.Field { + switch attr.Value.Type() { + case attribute.BOOL: + return zap.Bool(AttributeKey(attr.Key), attr.Value.AsBool()) + case attribute.BOOLSLICE: + return zap.Bools(AttributeKey(attr.Key), attr.Value.AsBoolSlice()) + case attribute.INT64: + return zap.Int64(AttributeKey(attr.Key), attr.Value.AsInt64()) + case attribute.INT64SLICE: + return zap.Int64s(AttributeKey(attr.Key), attr.Value.AsInt64Slice()) + case attribute.FLOAT64: + return zap.Float64(AttributeKey(attr.Key), attr.Value.AsFloat64()) + case attribute.FLOAT64SLICE: + return zap.Float64s(AttributeKey(attr.Key), attr.Value.AsFloat64Slice()) + case attribute.STRING: + return zap.String(AttributeKey(attr.Key), attr.Value.AsString()) + case attribute.STRINGSLICE: + if value := attr.Value.AsStringSlice(); len(value) == 1 { + return zap.String(AttributeKey(attr.Key), value[0]) + } else { + return zap.Strings(AttributeKey(attr.Key), value) + } + default: + return zap.Any(AttributeKey(attr.Key), attr.Value.AsInterface()) + } +} + +func AttributeKey(key attribute.Key) string { + return strings.ReplaceAll(string(key), ".", "_") +} diff --git a/log/with.go b/log/with.go index 24d9c28..d8d0a39 100644 --- a/log/with.go +++ b/log/with.go @@ -7,7 +7,9 @@ import ( "net/http" "strings" + keelsemconv "github.com/foomo/keel/semconv" "go.opentelemetry.io/otel/attribute" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" @@ -40,7 +42,7 @@ func WithError(l *zap.Logger, err error) *zap.Logger { } func WithServiceName(l *zap.Logger, name string) *zap.Logger { - return With(l, FServiceName(name)) + return With(l, Attribute(semconv.ServiceName(name))) } func WithTraceID(l *zap.Logger, ctx context.Context) *zap.Logger { @@ -56,29 +58,22 @@ func WithHTTPServerName(l *zap.Logger, name string) *zap.Logger { } func WithHTTPFlavor(l *zap.Logger, r *http.Request) *zap.Logger { - switch r.ProtoMajor { - case 1: - return With(l, FHTTPFlavor(fmt.Sprintf("1.%d", r.ProtoMinor))) - case 2: - return With(l, FHTTPFlavor("2")) - default: - return l - } + return With(l, Attributes(semconv.NetworkProtocolName("HTTP"), semconv.NetworkProtocolVersion(fmt.Sprintf("%d.%d", r.ProtoMajor, r.ProtoMinor)))...) } func WithHTTPScheme(l *zap.Logger, r *http.Request) *zap.Logger { if r.TLS != nil { - return With(l, FHTTPScheme("https")) + return With(l, Attribute(semconv.URLScheme("https"))) } else { - return With(l, FHTTPScheme("http")) + return With(l, Attribute(semconv.URLScheme("http"))) } } func WithHTTPSessionID(l *zap.Logger, r *http.Request) *zap.Logger { if id := r.Header.Get("X-Session-Id"); id != "" { - return With(l, FHTTPSessionID(id)) + return With(l, Attribute(semconv.SessionID(id))) } else if id, ok := keelhttpcontext.GetSessionID(r.Context()); ok && id != "" { - return With(l, FHTTPSessionID(id)) + return With(l, Attribute(semconv.SessionID(id))) } else { return l } @@ -86,9 +81,9 @@ func WithHTTPSessionID(l *zap.Logger, r *http.Request) *zap.Logger { func WithHTTPRequestID(l *zap.Logger, r *http.Request) *zap.Logger { if id := r.Header.Get("X-Request-Id"); id != "" { - return With(l, FHTTPRequestID(id)) + return With(l, Attribute(keelsemconv.HTTPXRequestID(id))) } else if id, ok := keelhttpcontext.GetRequestID(r.Context()); ok && id != "" { - return With(l, FHTTPRequestID(id)) + return With(l, Attribute(keelsemconv.HTTPXRequestID(id))) } else { return l } @@ -96,9 +91,9 @@ func WithHTTPRequestID(l *zap.Logger, r *http.Request) *zap.Logger { func WithHTTPReferer(l *zap.Logger, r *http.Request) *zap.Logger { if value := r.Header.Get("X-Referer"); value != "" { - return With(l, FHTTPReferer(value)) + return With(l, Attribute(keelsemconv.HTTPXRequestReferer(value))) } else if value := r.Referer(); value != "" { - return With(l, FHTTPReferer(value)) + return With(l, Attribute(keelsemconv.HTTPXRequestReferer(value))) } else { return l } @@ -106,19 +101,19 @@ func WithHTTPReferer(l *zap.Logger, r *http.Request) *zap.Logger { func WithHTTPHost(l *zap.Logger, r *http.Request) *zap.Logger { if value := r.Header.Get("X-Forwarded-Host"); value != "" { - return With(l, FHTTPHost(value)) + return With(l, Attribute(semconv.HostName(value))) } else if !r.URL.IsAbs() { - return With(l, FHTTPHost(r.Host)) + return With(l, Attribute(semconv.HostName(r.Host))) } else { - return With(l, FHTTPHost(r.URL.Host)) + return With(l, Attribute(semconv.HostName(r.URL.Host))) } } func WithHTTPTrackingID(l *zap.Logger, r *http.Request) *zap.Logger { if id := r.Header.Get("X-Tracking-Id"); id != "" { - return With(l, FHTTPTrackingID(id)) + return With(l, Attribute(keelsemconv.TrackingID(id))) } else if id, ok := keelhttpcontext.GetTrackingID(r.Context()); ok && id != "" { - return With(l, FHTTPTrackingID(id)) + return With(l, Attribute(keelsemconv.TrackingID(id))) } else { return l } @@ -142,7 +137,7 @@ func WithHTTPClientIP(l *zap.Logger, r *http.Request) *zap.Logger { } if clientIP != "" { - return With(l, FHTTPClientIP(clientIP)) + return With(l, Attribute(semconv.ClientAddress(clientIP))) } return l @@ -159,12 +154,12 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger { l = WithHTTPClientIP(l, r) l = WithTraceID(l, r.Context()) - return With(l, - FHTTPMethod(r.Method), - FHTTPTarget(r.RequestURI), - FHTTPUserAgent(r.UserAgent()), - FHTTPRequestContentLength(r.ContentLength), - ) + return With(l, Attributes( + semconv.URLPath(r.URL.Path), + semconv.UserAgentName(r.UserAgent()), + semconv.HTTPRequestMethodKey.String(r.Method), + semconv.HTTPRequestSizeKey.Int64(r.ContentLength), + )...) } func WithHTTPRequestOut(l *zap.Logger, r *http.Request) *zap.Logger { @@ -176,9 +171,10 @@ func WithHTTPRequestOut(l *zap.Logger, r *http.Request) *zap.Logger { l = WithHTTPFlavor(l, r) l = WithTraceID(l, r.Context()) - return With(l, - FHTTPMethod(r.Method), - FHTTPTarget(r.URL.Path), - FHTTPWroteBytes(r.ContentLength), - ) + return With(l, Attributes( + semconv.URLPath(r.URL.Path), + semconv.UserAgentName(r.UserAgent()), + semconv.HTTPRequestMethodKey.String(r.Method), + semconv.HTTPRequestSizeKey.Int64(r.ContentLength), + )...) } diff --git a/net/http/middleware/logger.go b/net/http/middleware/logger.go index 81efdf4..263acef 100644 --- a/net/http/middleware/logger.go +++ b/net/http/middleware/logger.go @@ -5,6 +5,7 @@ import ( "time" httplog "github.com/foomo/keel/net/http/log" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" @@ -98,8 +99,8 @@ func LoggerWithOptions(opts LoggerOptions) Middleware { l = l.With( log.FDuration(time.Since(start)), - log.FHTTPStatusCode(wr.StatusCode()), - log.FHTTPWroteBytes(int64(wr.Size())), + log.Attribute(semconv.HTTPResponseStatusCode(wr.StatusCode())), + log.Attribute(semconv.HTTPResponseSize(wr.Size())), ) if labeler != nil { diff --git a/net/http/roundtripware/logger.go b/net/http/roundtripware/logger.go index 3d78967..9d0384e 100644 --- a/net/http/roundtripware/logger.go +++ b/net/http/roundtripware/logger.go @@ -4,6 +4,7 @@ import ( "net/http" "time" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" @@ -86,10 +87,10 @@ func Logger(opts ...LoggerOption) RoundTripware { if err != nil { l = log.WithError(l, err) } else if resp != nil { - l = log.With(l, - log.FHTTPStatusCode(resp.StatusCode), - log.FHTTPRequestContentLength(resp.ContentLength), - ) + l = log.With(l, log.Attributes( + semconv.HTTPResponseStatusCode(resp.StatusCode), + semconv.HTTPResponseBodySizeKey.Int64(resp.ContentLength), + )...) statusCode = resp.StatusCode } diff --git a/net/stream/jetstream/stream.go b/net/stream/jetstream/stream.go index 803f2c9..7391dc9 100644 --- a/net/stream/jetstream/stream.go +++ b/net/stream/jetstream/stream.go @@ -7,6 +7,7 @@ import ( "github.com/nats-io/nats.go" "github.com/pkg/errors" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" "github.com/foomo/keel/log" @@ -122,7 +123,7 @@ func SubscriberWithUnmarshal(unmarshal UnmarshalFn) SubscriberOption { func New(l *zap.Logger, name, addr string, opts ...Option) (*Stream, error) { stream := &Stream{ l: l.With( - log.FMessagingSystem("jetstream"), + log.Attribute(semconv.MessagingSystemKey.String("jetstream")), log.FName(name), ), name: name, @@ -294,7 +295,11 @@ func (s *Stream) connect() error { func (s *Stream) initNatsOptions() { natsOpts := append([]nats.Option{ nats.ErrorHandler(func(conn *nats.Conn, subscription *nats.Subscription, err error) { - s.l.Error("nats error", log.FError(err), log.FStreamQueue(subscription.Queue), log.FStreamSubject(subscription.Subject)) + s.l.Error("nats error", + log.FError(err), + log.Attribute(semconv.MessagingDestinationName(subscription.Queue)), + log.Attribute(semconv.MessagingDestinationSubscriptionName(subscription.Subject)), + ) }), nats.ClosedHandler(func(conn *nats.Conn) { if err := conn.LastError(); err != nil { diff --git a/semconv/http.go b/semconv/http.go new file mode 100644 index 0000000..ca490e2 --- /dev/null +++ b/semconv/http.go @@ -0,0 +1,22 @@ +package semconv + +import ( + "go.opentelemetry.io/otel/attribute" +) + +const ( + // HTTPXRequestIDKey is the key for http.request.id. + HTTPXRequestIDKey = attribute.Key("http.request.id") + // HTTPXRequestRefererKey is the key for http.request.referer. + HTTPXRequestRefererKey = attribute.Key("http.request.referer") +) + +// HTTPXRequestID returns a new attribute.KeyValue for http.request.id. +func HTTPXRequestID(v string) attribute.KeyValue { + return HTTPXRequestIDKey.String(v) +} + +// HTTPXRequestReferer returns a new attribute.KeyValue for http.request.referer. +func HTTPXRequestReferer(v string) attribute.KeyValue { + return HTTPXRequestRefererKey.String(v) +} diff --git a/semconv/keel.go b/semconv/keel.go new file mode 100644 index 0000000..64c4113 --- /dev/null +++ b/semconv/keel.go @@ -0,0 +1,29 @@ +package semconv + +import ( + "go.opentelemetry.io/otel/attribute" +) + +const ( + // KeelServiceTypeKey is the key for keel.service.type. + KeelServiceTypeKey = attribute.Key("keel.service.type") + // KeelServiceNameKey is the key for keel.service.name. + KeelServiceNameKey = attribute.Key("keel.service.name") + // KeelServiceInstKey is the key for keel.service.inst. + KeelServiceInstKey = attribute.Key("keel.service.inst") +) + +// KeelServiceType returns a new attribute.KeyValue for keel.service.type. +func KeelServiceType(v string) attribute.KeyValue { + return KeelServiceTypeKey.String(v) +} + +// KeelServiceName returns a new attribute.KeyValue for keel.service.name. +func KeelServiceName(v string) attribute.KeyValue { + return KeelServiceNameKey.String(v) +} + +// KeelServiceInst returns a new attribute.KeyValue for keel.service.inst. +func KeelServiceInst(v int) attribute.KeyValue { + return KeelServiceInstKey.Int(v) +} diff --git a/semconv/tracking.go b/semconv/tracking.go new file mode 100644 index 0000000..2f14d75 --- /dev/null +++ b/semconv/tracking.go @@ -0,0 +1,15 @@ +package semconv + +import ( + "go.opentelemetry.io/otel/attribute" +) + +const ( + // TrackingIDKey is the key for tracking.id. + TrackingIDKey = attribute.Key("tracking.id") +) + +// TrackingID returns a new attribute.KeyValue for tracking.id. +func TrackingID(v string) attribute.KeyValue { + return TrackingIDKey.String(v) +} diff --git a/service/goroutine.go b/service/goroutine.go index f003c12..f8f7831 100644 --- a/service/goroutine.go +++ b/service/goroutine.go @@ -6,6 +6,7 @@ import ( "sync" "sync/atomic" + "github.com/foomo/keel/semconv" "go.uber.org/zap" "golang.org/x/sync/errgroup" @@ -34,8 +35,8 @@ func NewGoRoutine(l *zap.Logger, name string, handler GoRoutineFn, opts ...GoRou } // enrich the log l = log.WithAttributes(l, - log.KeelServiceTypeKey.String("goroutine"), - log.KeelServiceNameKey.String(name), + semconv.KeelServiceType("goroutine"), + semconv.KeelServiceType(name), ) inst := &GoRoutine{ @@ -92,7 +93,7 @@ func (s *GoRoutine) Start(ctx context.Context) error { s.cancelLock.Unlock() for i := range s.parallel { - l := log.WithAttributes(s.l, log.KeelServiceInstKey.Int(i)) + l := log.WithAttributes(s.l, semconv.KeelServiceInst(i)) s.wg.Go(func() error { return s.handler(ctx, l) }) diff --git a/service/http.go b/service/http.go index 57f1342..e181a37 100644 --- a/service/http.go +++ b/service/http.go @@ -9,7 +9,9 @@ import ( "sync/atomic" "time" + keelsemconv "github.com/foomo/keel/semconv" "github.com/pkg/errors" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" "github.com/foomo/keel/log" @@ -34,8 +36,8 @@ func NewHTTP(l *zap.Logger, name, addr string, handler http.Handler, middlewares } // enrich the log l = log.WithAttributes(l, - log.KeelServiceTypeKey.String("http"), - log.KeelServiceNameKey.String(name), + keelsemconv.KeelServiceType("http"), + keelsemconv.KeelServiceName(name), ) return &HTTP{ @@ -87,7 +89,7 @@ func (s *HTTP) Start(ctx context.Context) error { ip = "0.0.0.0" } - fields = append(fields, log.FNetHostIP(ip), log.FNetHostPort(port)) + fields = append(fields, log.Attributes(semconv.ServerAddress(ip), semconv.ServerPortKey.String(port))...) } s.l.Info("starting keel service", fields...) diff --git a/service/httphealthz.go b/service/httphealthz.go index a508900..3193f30 100644 --- a/service/httphealthz.go +++ b/service/httphealthz.go @@ -8,6 +8,7 @@ import ( "github.com/foomo/keel/healthz" "github.com/foomo/keel/interfaces" "github.com/foomo/keel/log" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" ) @@ -30,7 +31,7 @@ func NewHealthz(l *zap.Logger, name, addr, path string, probes map[healthz.Type] unavailable := func(l *zap.Logger, w http.ResponseWriter, r *http.Request, err error) { if err != nil { - log.WithError(l, err).With(log.FHTTPTarget(r.RequestURI)).Debug("healthz probe failed") + log.WithError(l, err).With(log.Attribute(semconv.URLFull(r.RequestURI))).Debug("healthz probe failed") http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) } } diff --git a/telemetry/ctx.go b/telemetry/context.go similarity index 100% rename from telemetry/ctx.go rename to telemetry/context.go index 6f590ed..ccea6a1 100644 --- a/telemetry/ctx.go +++ b/telemetry/context.go @@ -16,6 +16,10 @@ type Context struct { context.Context } +func Ctx(ctx context.Context) Context { + return Context{ctx} +} + func (c Context) Log() *zap.Logger { return Log(c.Context) } @@ -54,7 +58,3 @@ func (c Context) Profile(handler func(ctx Context), labels ...string) { handler(Ctx(ctx)) }) } - -func Ctx(ctx context.Context) Context { - return Context{ctx} -} diff --git a/telemetry/resource.go b/telemetry/resource.go index 43f75af..f44b130 100644 --- a/telemetry/resource.go +++ b/telemetry/resource.go @@ -22,7 +22,7 @@ func NewResource(ctx context.Context) (*resource.Resource, error) { semconv.VCSRefHeadNameKey: {"GIT_BRANCH", "OTEL_VCS_HEAD_NAME"}, semconv.VCSRefHeadRevisionKey: {"GIT_COMMIT", "GIT_COMMIT_HASH", "OTEL_VCS_HEAD_REVSION"}, semconv.VCSRefHeadTypeKey: {"GIT_TYPE", "OTEL_VCS_HEAD_TYPE"}, - "vcs_root_path": {"OTEL_VCS_ROOT_PATH"}, + "vcs_root_path": {"REPO_PATH", "REPOSITORY_PATH", "GIT_REPOSITORY_PATH", "OTEL_VCS_ROOT_PATH"}, } for k, keys := range envs { for _, key := range keys { diff --git a/utils/net/http/errors.go b/utils/net/http/errors.go index 0bcb462..962a305 100644 --- a/utils/net/http/errors.go +++ b/utils/net/http/errors.go @@ -5,6 +5,7 @@ import ( httplog "github.com/foomo/keel/net/http/log" "github.com/pkg/errors" + semconv "go.opentelemetry.io/otel/semconv/v1.37.0" "go.uber.org/zap" "github.com/foomo/keel/log" @@ -48,7 +49,7 @@ func ServerError(l *zap.Logger, w http.ResponseWriter, r *http.Request, code int } else { l = log.WithError(l, err) l = log.WithHTTPRequest(l, r) - l.Error("http server error", log.FHTTPStatusCode(code)) + l.Error("http server error", log.Attribute(semconv.HTTPResponseStatusCode(code))) } // w.Header().Set(keelhttp.HeaderXError, err.Error()) TODO make configurable with better value http.Error(w, http.StatusText(code), code)