-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TraceID and SpanID are both "00000...." in otel log export #6114
Comments
@MrAlias can you please help? |
Missing repro steps |
I'm sorry for the delay. Here are the steps for repro.
func newResource(name string) (*resource.Resource, error) {
return resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName(name),
),
)
}
func NewTracer(ctx context.Context, name string, ex *Exporter) (*Tracer, error) {
// setup exporter based on service choice
e, err := setupTraceExporter(ctx, ex)
if err != nil {
return nil, err
}
provider, err := newTraceProvider(e, name)
if err != nil {
return nil, err
}
// set global provider for all libraries using otel - otelmux etc.
otel.SetTracerProvider(provider)
otel.SetTextMapPropagator(propagation.TraceContext{})
tracer := provider.Tracer(name)
return &Tracer{name: name, tracer: tracer, provider: provider}, nil
}
func newTraceProvider(exp sdktrace.SpanExporter, name string) (*sdktrace.TracerProvider, error) {
r, err := newResource(name)
if err != nil {
return nil, err
}
return sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithBatcher(exp),
sdktrace.WithResource(r),
), nil
}
func setupTraceExporter(ctx context.Context, ex *Exporter) (e sdktrace.SpanExporter, err error) {
switch ex.Type {
case ConsoleExporter:
e, err = newTraceConsoleExporter()
case OTLPHttpExporter:
e, err = newTraceOTLPHttpExporter(ctx, ex.HttpEndpoint)
case OTLPGrpcExporter:
e, err = newTraceOTLPGrpcExporter(ctx, ex.GrcpEndpoint)
default:
e, err = newTraceConsoleExporter()
}
if err != nil {
return nil, err
}
return
}
func newTraceOTLPHttpExporter(ctx context.Context, otlpEndpoint string) (sdktrace.SpanExporter, error) {
insecureOpts := otlptracehttp.WithInsecure()
endpoint := otlptracehttp.WithEndpoint(otlpEndpoint)
return otlptracehttp.New(ctx, endpoint, insecureOpts)
}
func newTraceOTLPGrpcExporter(ctx context.Context, otlpEndpoint string) (sdktrace.SpanExporter, error) {
insecureOpts := otlptracegrpc.WithInsecure()
endpoint := otlptracegrpc.WithEndpoint(otlpEndpoint)
return otlptracegrpc.New(ctx, endpoint, insecureOpts)
}
func newTraceConsoleExporter() (sdktrace.SpanExporter, error) {
return stdouttrace.New(stdouttrace.WithPrettyPrint())
}
func NewLogger(ctx context.Context, name string, exp *Exporter) (*Logger, error) {
ex, err := setupLogExporter(ctx, exp)
if err != nil {
return nil, err
}
provider, err := newLogProvider(ex, name)
if err != nil {
return nil, err
}
return &Logger{name: name, provider: provider}, nil
}
func newLogProvider(exp sdklogs.Exporter, name string) (*sdklogs.LoggerProvider, error) {
r, err := newResource(name)
if err != nil {
return nil, err
}
processor := sdklogs.NewBatchProcessor(exp)
// TODO - add sampling option
return sdklogs.NewLoggerProvider(
sdklogs.WithResource(r),
sdklogs.WithProcessor(processor),
), nil
}
func setupLogExporter(ctx context.Context, ex *Exporter) (e sdklogs.Exporter, err error) {
switch ex.Type {
case ConsoleExporter:
e, err = newLogConsoleExporter()
case OTLPHttpExporter:
e, err = newLogOTLPHttpExporter(ctx, ex.HttpEndpoint)
case OTLPGrpcExporter:
e, err = newLogOTLPGrpcExporter(ctx, ex.GrcpEndpoint)
default:
e, err = newLogConsoleExporter()
}
if err != nil {
return nil, err
}
return
}
func newLogOTLPHttpExporter(ctx context.Context, otlpEndpoint string) (sdklogs.Exporter, error) {
insecureOpts := otlploghttp.WithInsecure()
endpoint := otlploghttp.WithEndpoint(otlpEndpoint)
return otlploghttp.New(ctx, endpoint, insecureOpts)
}
func newLogOTLPGrpcExporter(ctx context.Context, otlpEndpoint string) (sdklogs.Exporter, error) {
insecureOpts := otlploggrpc.WithInsecure()
endpoint := otlploggrpc.WithEndpoint(otlpEndpoint)
return otlploggrpc.New(ctx, endpoint, insecureOpts)
}
func newLogConsoleExporter() (sdklogs.Exporter, error) {
return stdoutlog.New(stdoutlog.WithPrettyPrint())
}
func (l *Logger) NewLoggerCore() *otelzap.Core {
core := otelzap.NewCore(l.name, otelzap.WithLoggerProvider(l.provider))
return core
}
func NewWithService(service, logLevel string, core ...zapcore.Core) (*Logger, error) {
level, err := zap.ParseAtomicLevel(logLevel)
if err != nil {
return nil, err
}
config := zap.Config{
Level: level,
Encoding: "json",
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
InitialFields: map[string]interface{}{"service": service},
EncoderConfig: zap.NewProductionEncoderConfig(),
DisableStacktrace: true,
}
if level.Level() == zap.DebugLevel {
config.EncoderConfig = zap.NewDevelopmentEncoderConfig()
}
log, err := config.Build()
if err != nil {
return nil, err
}
if len(core) > 0 {
otelCore := zap.WrapCore(func(c zapcore.Core) zapcore.Core {
core = append(core, c)
return zapcore.NewTee(core...)
})
log = log.WithOptions(otelCore)
}
return &Logger{log}, nil
} Then using the logger throughout the service Another thing also worth mentioning is that, I have built custom wrappers on top of zap to inject trace events on Logging a event. type LoggerWithCtx struct {
ctx context.Context
l *Logger
}
func (l *Logger) Ctx(ctx context.Context) *LoggerWithCtx {
span := trace.SpanContextFromContext(ctx)
traceID := zap.String("trace_id", span.TraceID().String())
spanID := zap.String("span_id", span.SpanID().String())
fields := []zap.Field{traceID, spanID}
return &LoggerWithCtx{ctx, &Logger{l.logger.With(fields...)}}
}
func (l *LoggerWithCtx) Debug(msg string, fields ...zap.Field) {
if span := trace.SpanFromContext(l.ctx); span.IsRecording() {
span.AddEvent(msg)
}
l.l.logger.Debug(msg, fields...)
}
func (l *LoggerWithCtx) Info(msg string, fields ...zap.Field) {
if span := trace.SpanFromContext(l.ctx); span.IsRecording() {
span.AddEvent(msg)
}
l.l.logger.Info(msg, fields...)
}
func (l *LoggerWithCtx) Error(msg string, fields ...zap.Field) {
if span := trace.SpanFromContext(l.ctx); span.IsRecording() {
span.SetStatus(codes.Error, msg)
}
l.l.logger.Error(msg, fields...)
}
func (l *LoggerWithCtx) Log(level zapcore.Level, msg string, fields ...zap.Field) {
l.l.logger.Log(level, msg, fields...)
}
func (l *LoggerWithCtx) With(fields ...zap.Field) *LoggerWithCtx {
log := l.l.logger.With(fields...)
return &LoggerWithCtx{l.ctx, &Logger{logger: log}}
} |
@MrAlias can you please help me out? |
Look like a otelzap bridge issue, I would recommend reopening an issue in https://github.com/open-telemetry/opentelemetry-go-contrib Otelzap does this magic where if you pass a context in as a zap value it would treat it as the ctx when emitting the log line. I think it defaults to a ctx without a span and would result to empty trace/span ids. I think a way to fix your current could be:
This magic is introduced to help pass ctx from zap to Otel logger since zap does not support contextful log natively. |
Description
I'm using a otelzap bridge for logging and for testing purposes I've configures a console exporter for both traces and metrics
while logging I manually extract traceID and spanID from the context and add them as zap fields, but when otel exports the logs to console I can see that the traceIDs and spanIDs are added as attributes but the keys
TraceID
andSpanID
are both zeros everytime. I've no idea why is that so.Environment
Expected behavior
Zap Logs:
Otel Logs:
The text was updated successfully, but these errors were encountered: