feat: switch to semconv

This commit is contained in:
Kevin Franklin Kim 2025-10-08 15:43:32 +02:00
parent 80d236f6a2
commit b4b775681a
No known key found for this signature in database
25 changed files with 310 additions and 156 deletions

View File

@ -7,6 +7,7 @@ import (
"net/http/httptest" "net/http/httptest"
"strings" "strings"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/foomo/keel/log" "github.com/foomo/keel/log"
@ -58,7 +59,7 @@ func (s *ServiceHTTP) Start(ctx context.Context) error {
ip = "0.0.0.0" 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...) s.l.Info("starting http test service", fields...)

View File

@ -18,22 +18,22 @@ const (
JSONKey = "json" 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 { func FNum(num int) zap.Field {
return zap.Int(NumKey, num) 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 { func FName(name string) zap.Field {
return zap.String(NameKey, name) 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 { func FValue(value interface{}) zap.Field {
return zap.String(ValueKey, fmt.Sprintf("%v", value)) 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 { func FJSON(v interface{}) zap.Field {
if out, err := json.Marshal(v); err != nil { if out, err := json.Marshal(v); err != nil {
return zap.String(JSONKey+"_error", err.Error()) return zap.String(JSONKey+"_error", err.Error())

View File

@ -5,24 +5,32 @@ import (
) )
const ( const (
// Deprecated: use semconv messaging attributes instead.
CodeInstanceKey = "code_instance" CodeInstanceKey = "code_instance"
CodePackageKey = "code_package" // Deprecated: use semconv messaging attributes instead.
CodeMethodKey = "code_method" CodePackageKey = "code_package"
CodeLineKey = "code_line" // 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 { func FCodeInstance(v string) zap.Field {
return zap.String(CodeInstanceKey, v) return zap.String(CodeInstanceKey, v)
} }
// Deprecated: use semconv messaging attributes instead.
func FCodePackage(v string) zap.Field { func FCodePackage(v string) zap.Field {
return zap.String(CodePackageKey, v) return zap.String(CodePackageKey, v)
} }
// Deprecated: use semconv messaging attributes instead.
func FCodeMethod(v string) zap.Field { func FCodeMethod(v string) zap.Field {
return zap.String(CodeMethodKey, v) return zap.String(CodeMethodKey, v)
} }
// Deprecated: use semconv messaging attributes instead.
func FCodeLine(v int) zap.Field { func FCodeLine(v int) zap.Field {
return zap.Int(CodeLineKey, v) return zap.Int(CodeLineKey, v)
} }

View File

@ -7,28 +7,37 @@ import (
) )
const ( const (
DurationKey = "duration" // DurationKey - generic duration attribute
DurationSecKey = "duration_sec" DurationKey = "duration"
DurationMinKey = "duration_min" // DurationSecKey - duration in seconds
DurationSecKey = "duration_sec"
// DurationMinKey - duration in minutes
DurationMinKey = "duration_min"
// DurationHourKey - duration in hours
DurationHourKey = "duration_hour" 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 { 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 { 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 { 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 { 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 { func FDurationFn() func() zap.Field {
start := time.Now() start := time.Now()

View File

@ -5,110 +5,109 @@ import (
) )
const ( const (
// HTTPServerNameKey represents the name of the service handling the request // Deprecated: use semconv messaging attributes instead.
HTTPServerNameKey = "http_server_name" HTTPServerNameKey = "http_server_name"
// Deprecated: use semconv messaging attributes instead.
// HTTPMethodKey represents the HTTP request method.
HTTPMethodKey = "http_method" HTTPMethodKey = "http_method"
// Deprecated: use semconv messaging attributes instead.
// HTTPTargetKey represents the full request target as passed in a HTTP
// request line or equivalent, e.g. "/path/12314/?q=ddds#123".
HTTPTargetKey = "http_target" HTTPTargetKey = "http_target"
// Deprecated: use semconv messaging attributes instead.
// HTTPHostKey represents the value of the HTTP host header.
HTTPHostKey = "http_host" HTTPHostKey = "http_host"
// Deprecated: use semconv messaging attributes instead.
// HTTPStatusCodeKey represents the HTTP response status code.
HTTPStatusCodeKey = "http_status_code" HTTPStatusCodeKey = "http_status_code"
// Deprecated: use semconv messaging attributes instead.
// HTTPUserAgentKey represents the Value of the HTTP User-Agent header sent by the client.
HTTPUserAgentKey = "http_user_agent" HTTPUserAgentKey = "http_user_agent"
// Deprecated: use semconv messaging attributes instead.
// HTTPClientIPKey represents the IP address of the original client behind all proxies,
// if known (e.g. from X-Forwarded-For).
HTTPClientIPKey = "http_client_ip" HTTPClientIPKey = "http_client_ip"
// Deprecated: use semconv messaging attributes instead.
// HTTPRequestContentLengthKey represents the size of the request payload body in bytes.
HTTPRequestContentLengthKey = "http_read_bytes" HTTPRequestContentLengthKey = "http_read_bytes"
// Deprecated: use semconv messaging attributes instead.
// HTTPWroteBytesKey represents the size of the response payload body in bytes.
HTTPWroteBytesKey = "http_wrote_bytes" // #nosec HTTPWroteBytesKey = "http_wrote_bytes" // #nosec
// Deprecated: use semconv messaging attributes instead.
// HTTPSchemeKey represents the URI scheme identifying the used protocol.
HTTPSchemeKey = "http_scheme" HTTPSchemeKey = "http_scheme"
// Deprecated: use semconv messaging attributes instead.
// HTTPFlavorKey represents the Kind of HTTP protocol used.
HTTPFlavorKey = "http_flavor" HTTPFlavorKey = "http_flavor"
// Deprecated: use semconv messaging attributes instead.
// HTTPRequestIDKey represents the HTTP request id if known (e.g. from X-Request-ID).
HTTPRequestIDKey = "http_request_id" HTTPRequestIDKey = "http_request_id"
// Deprecated: use semconv messaging attributes instead.
// HTTPSessionIDKey represents the HTTP session id if known (e.g. from X-Session-ID).
HTTPSessionIDKey = "http_session_id" HTTPSessionIDKey = "http_session_id"
// Deprecated: use semconv messaging attributes instead.
// HTTPTrackingIDKey represents the HTTP tracking id if known (e.g. from X-Tracking-ID).
HTTPTrackingIDKey = "http_tracking_id" HTTPTrackingIDKey = "http_tracking_id"
// Deprecated: use semconv messaging attributes instead.
// HTTPRefererKey identifies the address of the web page (i.e., the URI or IRI), from which the resource has been requested.
HTTPRefererKey = "http_referer" HTTPRefererKey = "http_referer"
) )
// Deprecated: use semconv messaging attributes instead.
func FHTTPServerName(id string) zap.Field { func FHTTPServerName(id string) zap.Field {
return zap.String(HTTPServerNameKey, id) return zap.String(HTTPServerNameKey, id)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPRequestID(id string) zap.Field { func FHTTPRequestID(id string) zap.Field {
return zap.String(HTTPRequestIDKey, id) return zap.String(HTTPRequestIDKey, id)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPSessionID(id string) zap.Field { func FHTTPSessionID(id string) zap.Field {
return zap.String(HTTPSessionIDKey, id) return zap.String(HTTPSessionIDKey, id)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPTrackingID(id string) zap.Field { func FHTTPTrackingID(id string) zap.Field {
return zap.String(HTTPTrackingIDKey, id) return zap.String(HTTPTrackingIDKey, id)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPRequestContentLength(bytes int64) zap.Field { func FHTTPRequestContentLength(bytes int64) zap.Field {
return zap.Int64(HTTPRequestContentLengthKey, bytes) return zap.Int64(HTTPRequestContentLengthKey, bytes)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPWroteBytes(bytes int64) zap.Field { func FHTTPWroteBytes(bytes int64) zap.Field {
return zap.Int64(HTTPWroteBytesKey, bytes) return zap.Int64(HTTPWroteBytesKey, bytes)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPStatusCode(status int) zap.Field { func FHTTPStatusCode(status int) zap.Field {
return zap.Int(HTTPStatusCodeKey, status) return zap.Int(HTTPStatusCodeKey, status)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPTarget(target string) zap.Field { func FHTTPTarget(target string) zap.Field {
return zap.String(HTTPTargetKey, target) return zap.String(HTTPTargetKey, target)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPClientIP(clientIP string) zap.Field { func FHTTPClientIP(clientIP string) zap.Field {
return zap.String(HTTPClientIPKey, clientIP) return zap.String(HTTPClientIPKey, clientIP)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPFlavor(flavor string) zap.Field { func FHTTPFlavor(flavor string) zap.Field {
return zap.String(HTTPFlavorKey, flavor) return zap.String(HTTPFlavorKey, flavor)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPScheme(scheme string) zap.Field { func FHTTPScheme(scheme string) zap.Field {
return zap.String(HTTPSchemeKey, scheme) return zap.String(HTTPSchemeKey, scheme)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPUserAgent(userAgent string) zap.Field { func FHTTPUserAgent(userAgent string) zap.Field {
return zap.String(HTTPUserAgentKey, userAgent) return zap.String(HTTPUserAgentKey, userAgent)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPReferer(host string) zap.Field { func FHTTPReferer(host string) zap.Field {
return zap.String(HTTPRefererKey, host) return zap.String(HTTPRefererKey, host)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPHost(host string) zap.Field { func FHTTPHost(host string) zap.Field {
return zap.String(HTTPHostKey, host) return zap.String(HTTPHostKey, host)
} }
// Deprecated: use semconv messaging attributes instead.
func FHTTPMethod(name string) zap.Field { func FHTTPMethod(name string) zap.Field {
return zap.String(HTTPMethodKey, name) return zap.String(HTTPMethodKey, name)
} }

View File

@ -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")
)

View File

@ -1,65 +1,89 @@
package log package log
import ( import (
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "go.uber.org/zap"
) )
const ( const (
MessagingSystemKey = "messaging_system" // Deprecated: use semconv messaging attributes instead.
MessagingDestinationKey = "messaging_destination" MessagingSystemKey = "messaging_system"
MessagingDestinationKindKey = "messaging_destination_kind" // Deprecated: use semconv messaging attributes instead.
MessagingProtocolKey = "messaging_protocol" MessagingDestinationKey = "messaging_destination"
MessagingProtocolVersionKey = "messaging_protocol_version" // Deprecated: use semconv messaging attributes instead.
MessagingURLKey = "messaging_url" MessagingDestinationKindKey = "messaging_destination_kind"
MessagingMessageIDKey = "messaging_message_id" // Deprecated: use semconv messaging attributes instead.
MessagingConversationIDKey = "messaging_conversation_id" MessagingProtocolKey = "messaging_protocol"
MessagingMessagePayloadSizeBytesKey = "messaging_message_payload_size_bytes" // 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" MessagingMessagePayloadCompressedSizeBytesKey = "messaging_message_payload_compressed_size_bytes"
) )
// Deprecated: use semconv messaging attributes instead.
type MessagingDestinationKind string type MessagingDestinationKind string
const ( const (
// Deprecated: use semconv messaging attributes instead.
MessagingDestinationKindQueue MessagingDestinationKind = "queue" MessagingDestinationKindQueue MessagingDestinationKind = "queue"
// Deprecated: use semconv messaging attributes instead.
MessagingDestinationKindTopic MessagingDestinationKind = "topic" MessagingDestinationKindTopic MessagingDestinationKind = "topic"
) )
// Deprecated: use semconv messaging attributes instead.
func FMessagingSystem(value string) zap.Field { func FMessagingSystem(value string) zap.Field {
return zap.String(MessagingSystemKey, value) return zap.String(MessagingSystemKey, value)
} }
// Deprecated: use semconv.MessagingDestinationName instead.
func FMessagingDestination(value string) zap.Field { 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 { func FMessagingDestinationKind(value MessagingDestinationKind) zap.Field {
return zap.String(MessagingDestinationKindKey, string(value)) return zap.String(MessagingDestinationKindKey, string(value))
} }
// Deprecated: use semconv messaging attributes instead.
func FMessagingProtocol(value string) zap.Field { func FMessagingProtocol(value string) zap.Field {
return zap.String(MessagingProtocolKey, value) return zap.String(MessagingProtocolKey, value)
} }
// Deprecated: use semconv messaging attributes instead.
func FMessagingProtocolVersion(value string) zap.Field { func FMessagingProtocolVersion(value string) zap.Field {
return zap.String(MessagingProtocolVersionKey, value) return zap.String(MessagingProtocolVersionKey, value)
} }
// Deprecated: use semconv messaging attributes instead.
func FMessagingURL(value string) zap.Field { func FMessagingURL(value string) zap.Field {
return zap.String(MessagingURLKey, value) return zap.String(MessagingURLKey, value)
} }
// Deprecated: use semconv.MessagingMessageID instead.
func FMessagingMessageID(value string) zap.Field { 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 { 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 { func FMessagingMessagePayloadSizeBytes(value string) zap.Field {
return zap.String(MessagingMessagePayloadSizeBytesKey, value) return zap.String(MessagingMessagePayloadSizeBytesKey, value)
} }
// Deprecated: use semconv messaging attributes instead.
func FMessagingMessagePayloadCompressedSizeBytes(value string) zap.Field { func FMessagingMessagePayloadCompressedSizeBytes(value string) zap.Field {
return zap.String(MessagingMessagePayloadCompressedSizeBytesKey, value) return zap.String(MessagingMessagePayloadCompressedSizeBytesKey, value)
} }

View File

@ -1,21 +1,23 @@
package log package log
import ( import (
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "go.uber.org/zap"
) )
const ( 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" NetHostIPKey = "net_host_ip"
// Deprecated: use semconv messaging attributes instead.
// NetHostPortKey represents the local host port.
NetHostPortKey = "net_host_port" NetHostPortKey = "net_host_port"
) )
// Deprecated: use semconv messaging attributes instead.
func FNetHostIP(ip string) zap.Field { 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 { func FNetHostPort(port string) zap.Field {
return zap.String(NetHostPortKey, port) return zap.String(NetHostPortKey, port)
} }

View File

@ -5,58 +5,53 @@ import (
) )
const ( const (
// PeerServiceKey represents the ServiceNameKey name of the remote service. // Deprecated: use semconv messaging attributes instead.
// Should equal the actual `service.name` resource attribute of the remote service, if any.
PeerServiceKey = "peer_service" 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 { func FPeerService(name string) zap.Field {
return zap.String(PeerServiceKey, name) return zap.String(PeerServiceKey, name)
} }
const ( // Deprecated: use semconv messaging attributes instead.
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"
)
func FServiceType(name string) zap.Field { func FServiceType(name string) zap.Field {
return zap.String(ServiceTypeKey, name) return zap.String(ServiceTypeKey, name)
} }
// Deprecated: use semconv messaging attributes instead.
func FServiceName(name string) zap.Field { func FServiceName(name string) zap.Field {
return zap.String(ServiceNameKey, name) return zap.String(ServiceNameKey, name)
} }
// Deprecated: use semconv messaging attributes instead.
func FServiceNamespace(namespace string) zap.Field { func FServiceNamespace(namespace string) zap.Field {
return zap.String(ServiceNamespaceKey, namespace) return zap.String(ServiceNamespaceKey, namespace)
} }
// Deprecated: use semconv messaging attributes instead.
func FServiceInstanceID(id string) zap.Field { func FServiceInstanceID(id string) zap.Field {
return zap.String(ServiceInstanceIDKey, id) return zap.String(ServiceInstanceIDKey, id)
} }
// Deprecated: use semconv messaging attributes instead.
func FServiceVersion(version string) zap.Field { func FServiceVersion(version string) zap.Field {
return zap.String(ServiceVersionKey, version) return zap.String(ServiceVersionKey, version)
} }
// Deprecated: use semconv messaging attributes instead.
func FServiceMethod(method string) zap.Field { func FServiceMethod(method string) zap.Field {
return zap.String(ServiceMethodKey, method) return zap.String(ServiceMethodKey, method)
} }

View File

@ -5,14 +5,18 @@ import (
) )
const ( const (
StreamQueueKey = "queue" // Deprecated: use semconv messaging attributes instead.
StreamQueueKey = "queue"
// Deprecated: use semconv messaging attributes instead.
StreamSubjectKey = "subject" StreamSubjectKey = "subject"
) )
// Deprecated: use semconv messaging attributes instead.
func FStreamQueue(queue string) zap.Field { func FStreamQueue(queue string) zap.Field {
return zap.String(StreamQueueKey, queue) return zap.String(StreamQueueKey, queue)
} }
// Deprecated: use semconv messaging attributes instead.
func FStreamSubject(name string) zap.Field { func FStreamSubject(name string) zap.Field {
return zap.String(StreamSubjectKey, name) return zap.String(StreamSubjectKey, name)
} }

View File

@ -2,6 +2,7 @@ package log
import ( import (
"fmt" "fmt"
"strings"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0" semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "go.uber.org/zap"
@ -41,8 +42,8 @@ func NewLogger(level, encoding string) *zap.Logger {
config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder config.EncoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder
} }
config.EncoderConfig.CallerKey = string(semconv.CodeFilePathKey) config.EncoderConfig.CallerKey = strings.ReplaceAll(string(semconv.CodeFilePathKey), ".", "_")
config.EncoderConfig.StacktraceKey = string(semconv.CodeStacktraceKey) config.EncoderConfig.StacktraceKey = strings.ReplaceAll(string(semconv.CodeStacktraceKey), ".", "_")
config.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) { config.EncoderConfig.EncodeCaller = func(caller zapcore.EntryCaller, encoder zapcore.PrimitiveArrayEncoder) {
encoder.AppendString(caller.File) encoder.AppendString(caller.File)
} }

48
log/otel.go Normal file
View File

@ -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), ".", "_")
}

View File

@ -7,7 +7,9 @@ import (
"net/http" "net/http"
"strings" "strings"
keelsemconv "github.com/foomo/keel/semconv"
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.uber.org/zap" "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 { 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 { 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 { func WithHTTPFlavor(l *zap.Logger, r *http.Request) *zap.Logger {
switch r.ProtoMajor { return With(l, Attributes(semconv.NetworkProtocolName("HTTP"), semconv.NetworkProtocolVersion(fmt.Sprintf("%d.%d", r.ProtoMajor, r.ProtoMinor)))...)
case 1:
return With(l, FHTTPFlavor(fmt.Sprintf("1.%d", r.ProtoMinor)))
case 2:
return With(l, FHTTPFlavor("2"))
default:
return l
}
} }
func WithHTTPScheme(l *zap.Logger, r *http.Request) *zap.Logger { func WithHTTPScheme(l *zap.Logger, r *http.Request) *zap.Logger {
if r.TLS != nil { if r.TLS != nil {
return With(l, FHTTPScheme("https")) return With(l, Attribute(semconv.URLScheme("https")))
} else { } else {
return With(l, FHTTPScheme("http")) return With(l, Attribute(semconv.URLScheme("http")))
} }
} }
func WithHTTPSessionID(l *zap.Logger, r *http.Request) *zap.Logger { func WithHTTPSessionID(l *zap.Logger, r *http.Request) *zap.Logger {
if id := r.Header.Get("X-Session-Id"); id != "" { 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 != "" { } else if id, ok := keelhttpcontext.GetSessionID(r.Context()); ok && id != "" {
return With(l, FHTTPSessionID(id)) return With(l, Attribute(semconv.SessionID(id)))
} else { } else {
return l 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 { func WithHTTPRequestID(l *zap.Logger, r *http.Request) *zap.Logger {
if id := r.Header.Get("X-Request-Id"); id != "" { 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 != "" { } else if id, ok := keelhttpcontext.GetRequestID(r.Context()); ok && id != "" {
return With(l, FHTTPRequestID(id)) return With(l, Attribute(keelsemconv.HTTPXRequestID(id)))
} else { } else {
return l 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 { func WithHTTPReferer(l *zap.Logger, r *http.Request) *zap.Logger {
if value := r.Header.Get("X-Referer"); value != "" { 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 != "" { } else if value := r.Referer(); value != "" {
return With(l, FHTTPReferer(value)) return With(l, Attribute(keelsemconv.HTTPXRequestReferer(value)))
} else { } else {
return l 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 { func WithHTTPHost(l *zap.Logger, r *http.Request) *zap.Logger {
if value := r.Header.Get("X-Forwarded-Host"); value != "" { 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() { } else if !r.URL.IsAbs() {
return With(l, FHTTPHost(r.Host)) return With(l, Attribute(semconv.HostName(r.Host)))
} else { } 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 { func WithHTTPTrackingID(l *zap.Logger, r *http.Request) *zap.Logger {
if id := r.Header.Get("X-Tracking-Id"); id != "" { 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 != "" { } else if id, ok := keelhttpcontext.GetTrackingID(r.Context()); ok && id != "" {
return With(l, FHTTPTrackingID(id)) return With(l, Attribute(keelsemconv.TrackingID(id)))
} else { } else {
return l return l
} }
@ -142,7 +137,7 @@ func WithHTTPClientIP(l *zap.Logger, r *http.Request) *zap.Logger {
} }
if clientIP != "" { if clientIP != "" {
return With(l, FHTTPClientIP(clientIP)) return With(l, Attribute(semconv.ClientAddress(clientIP)))
} }
return l return l
@ -159,12 +154,12 @@ func WithHTTPRequest(l *zap.Logger, r *http.Request) *zap.Logger {
l = WithHTTPClientIP(l, r) l = WithHTTPClientIP(l, r)
l = WithTraceID(l, r.Context()) l = WithTraceID(l, r.Context())
return With(l, return With(l, Attributes(
FHTTPMethod(r.Method), semconv.URLPath(r.URL.Path),
FHTTPTarget(r.RequestURI), semconv.UserAgentName(r.UserAgent()),
FHTTPUserAgent(r.UserAgent()), semconv.HTTPRequestMethodKey.String(r.Method),
FHTTPRequestContentLength(r.ContentLength), semconv.HTTPRequestSizeKey.Int64(r.ContentLength),
) )...)
} }
func WithHTTPRequestOut(l *zap.Logger, r *http.Request) *zap.Logger { 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 = WithHTTPFlavor(l, r)
l = WithTraceID(l, r.Context()) l = WithTraceID(l, r.Context())
return With(l, return With(l, Attributes(
FHTTPMethod(r.Method), semconv.URLPath(r.URL.Path),
FHTTPTarget(r.URL.Path), semconv.UserAgentName(r.UserAgent()),
FHTTPWroteBytes(r.ContentLength), semconv.HTTPRequestMethodKey.String(r.Method),
) semconv.HTTPRequestSizeKey.Int64(r.ContentLength),
)...)
} }

View File

@ -5,6 +5,7 @@ import (
"time" "time"
httplog "github.com/foomo/keel/net/http/log" httplog "github.com/foomo/keel/net/http/log"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.uber.org/zap" "go.uber.org/zap"
@ -98,8 +99,8 @@ func LoggerWithOptions(opts LoggerOptions) Middleware {
l = l.With( l = l.With(
log.FDuration(time.Since(start)), log.FDuration(time.Since(start)),
log.FHTTPStatusCode(wr.StatusCode()), log.Attribute(semconv.HTTPResponseStatusCode(wr.StatusCode())),
log.FHTTPWroteBytes(int64(wr.Size())), log.Attribute(semconv.HTTPResponseSize(wr.Size())),
) )
if labeler != nil { if labeler != nil {

View File

@ -4,6 +4,7 @@ import (
"net/http" "net/http"
"time" "time"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.opentelemetry.io/otel/trace" "go.opentelemetry.io/otel/trace"
"go.uber.org/zap" "go.uber.org/zap"
@ -86,10 +87,10 @@ func Logger(opts ...LoggerOption) RoundTripware {
if err != nil { if err != nil {
l = log.WithError(l, err) l = log.WithError(l, err)
} else if resp != nil { } else if resp != nil {
l = log.With(l, l = log.With(l, log.Attributes(
log.FHTTPStatusCode(resp.StatusCode), semconv.HTTPResponseStatusCode(resp.StatusCode),
log.FHTTPRequestContentLength(resp.ContentLength), semconv.HTTPResponseBodySizeKey.Int64(resp.ContentLength),
) )...)
statusCode = resp.StatusCode statusCode = resp.StatusCode
} }

View File

@ -7,6 +7,7 @@ import (
"github.com/nats-io/nats.go" "github.com/nats-io/nats.go"
"github.com/pkg/errors" "github.com/pkg/errors"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/foomo/keel/log" "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) { func New(l *zap.Logger, name, addr string, opts ...Option) (*Stream, error) {
stream := &Stream{ stream := &Stream{
l: l.With( l: l.With(
log.FMessagingSystem("jetstream"), log.Attribute(semconv.MessagingSystemKey.String("jetstream")),
log.FName(name), log.FName(name),
), ),
name: name, name: name,
@ -294,7 +295,11 @@ func (s *Stream) connect() error {
func (s *Stream) initNatsOptions() { func (s *Stream) initNatsOptions() {
natsOpts := append([]nats.Option{ natsOpts := append([]nats.Option{
nats.ErrorHandler(func(conn *nats.Conn, subscription *nats.Subscription, err error) { 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) { nats.ClosedHandler(func(conn *nats.Conn) {
if err := conn.LastError(); err != nil { if err := conn.LastError(); err != nil {

22
semconv/http.go Normal file
View File

@ -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)
}

29
semconv/keel.go Normal file
View File

@ -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)
}

15
semconv/tracking.go Normal file
View File

@ -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)
}

View File

@ -6,6 +6,7 @@ import (
"sync" "sync"
"sync/atomic" "sync/atomic"
"github.com/foomo/keel/semconv"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
@ -34,8 +35,8 @@ func NewGoRoutine(l *zap.Logger, name string, handler GoRoutineFn, opts ...GoRou
} }
// enrich the log // enrich the log
l = log.WithAttributes(l, l = log.WithAttributes(l,
log.KeelServiceTypeKey.String("goroutine"), semconv.KeelServiceType("goroutine"),
log.KeelServiceNameKey.String(name), semconv.KeelServiceType(name),
) )
inst := &GoRoutine{ inst := &GoRoutine{
@ -92,7 +93,7 @@ func (s *GoRoutine) Start(ctx context.Context) error {
s.cancelLock.Unlock() s.cancelLock.Unlock()
for i := range s.parallel { 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 { s.wg.Go(func() error {
return s.handler(ctx, l) return s.handler(ctx, l)
}) })

View File

@ -9,7 +9,9 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
keelsemconv "github.com/foomo/keel/semconv"
"github.com/pkg/errors" "github.com/pkg/errors"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/foomo/keel/log" "github.com/foomo/keel/log"
@ -34,8 +36,8 @@ func NewHTTP(l *zap.Logger, name, addr string, handler http.Handler, middlewares
} }
// enrich the log // enrich the log
l = log.WithAttributes(l, l = log.WithAttributes(l,
log.KeelServiceTypeKey.String("http"), keelsemconv.KeelServiceType("http"),
log.KeelServiceNameKey.String(name), keelsemconv.KeelServiceName(name),
) )
return &HTTP{ return &HTTP{
@ -87,7 +89,7 @@ func (s *HTTP) Start(ctx context.Context) error {
ip = "0.0.0.0" 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...) s.l.Info("starting keel service", fields...)

View File

@ -8,6 +8,7 @@ import (
"github.com/foomo/keel/healthz" "github.com/foomo/keel/healthz"
"github.com/foomo/keel/interfaces" "github.com/foomo/keel/interfaces"
"github.com/foomo/keel/log" "github.com/foomo/keel/log"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "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) { unavailable := func(l *zap.Logger, w http.ResponseWriter, r *http.Request, err error) {
if err != nil { 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) http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
} }
} }

View File

@ -16,6 +16,10 @@ type Context struct {
context.Context context.Context
} }
func Ctx(ctx context.Context) Context {
return Context{ctx}
}
func (c Context) Log() *zap.Logger { func (c Context) Log() *zap.Logger {
return Log(c.Context) return Log(c.Context)
} }
@ -54,7 +58,3 @@ func (c Context) Profile(handler func(ctx Context), labels ...string) {
handler(Ctx(ctx)) handler(Ctx(ctx))
}) })
} }
func Ctx(ctx context.Context) Context {
return Context{ctx}
}

View File

@ -22,7 +22,7 @@ func NewResource(ctx context.Context) (*resource.Resource, error) {
semconv.VCSRefHeadNameKey: {"GIT_BRANCH", "OTEL_VCS_HEAD_NAME"}, semconv.VCSRefHeadNameKey: {"GIT_BRANCH", "OTEL_VCS_HEAD_NAME"},
semconv.VCSRefHeadRevisionKey: {"GIT_COMMIT", "GIT_COMMIT_HASH", "OTEL_VCS_HEAD_REVSION"}, semconv.VCSRefHeadRevisionKey: {"GIT_COMMIT", "GIT_COMMIT_HASH", "OTEL_VCS_HEAD_REVSION"},
semconv.VCSRefHeadTypeKey: {"GIT_TYPE", "OTEL_VCS_HEAD_TYPE"}, 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 k, keys := range envs {
for _, key := range keys { for _, key := range keys {

View File

@ -5,6 +5,7 @@ import (
httplog "github.com/foomo/keel/net/http/log" httplog "github.com/foomo/keel/net/http/log"
"github.com/pkg/errors" "github.com/pkg/errors"
semconv "go.opentelemetry.io/otel/semconv/v1.37.0"
"go.uber.org/zap" "go.uber.org/zap"
"github.com/foomo/keel/log" "github.com/foomo/keel/log"
@ -48,7 +49,7 @@ func ServerError(l *zap.Logger, w http.ResponseWriter, r *http.Request, code int
} else { } else {
l = log.WithError(l, err) l = log.WithError(l, err)
l = log.WithHTTPRequest(l, r) 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 // w.Header().Set(keelhttp.HeaderXError, err.Error()) TODO make configurable with better value
http.Error(w, http.StatusText(code), code) http.Error(w, http.StatusText(code), code)