-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Aleksandr Nekrasov
committed
Sep 21, 2023
1 parent
67741e2
commit a774730
Showing
11 changed files
with
528 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Changelog | ||
|
||
All notable changes to this project will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | ||
|
||
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [Unreleased] | ||
|
||
## [v0.1.1] | ||
|
||
### Changed | ||
|
||
- opentelemetry-go updated to 1.17.0 | ||
- opentelemetry-logs-go updated to 0.2.0 | ||
- zap updated to 1.25.0 | ||
|
||
## [v0.1.0] 2023-08-06 | ||
|
||
### Fixed | ||
|
||
- using pointer to struct instead of raw struct | ||
|
||
### Changed | ||
|
||
- github.com/agoda-com/opentelemetry-logs-go updated to v0.1.2 | ||
|
||
## [v0.0.1] 2023-07-25 | ||
|
||
### Added | ||
|
||
- Otel Zap Core with OTLP format Log Records export | ||
- Ctx(ctx) method to provide context to underlying exporters | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# otelzap | ||
|
||
Zap logger with OpenTelemetry support. This logger will export LogRecord's in OTLP format. | ||
|
||
## Quick start | ||
|
||
[Export env variable](https://opentelemetry.io/docs/concepts/sdk-configuration/otlp-exporter-configuration/#otel_exporter_otlp_endpoint) `OTEL_EXPORTER_OTLP_ENDPOINT=https://localhost:4318` | ||
to your OTLP collector | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"context" | ||
"go.opentelemetry.io/otel" | ||
"go.opentelemetry.io/otel/sdk/resource" | ||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0" | ||
semconv2 "go.opentelemetry.io/otel/semconv/v1.4.0" | ||
"go.uber.org/zap" | ||
"github.com/agoda-com/otelzap" | ||
otellogs "github.com/agoda-com/opentelemetry-logs-go" | ||
sdk "github.com/agoda-com/opentelemetry-logs-go/sdk/logs" | ||
"github.com/agoda-com/opentelemetry-logs-go/exporters/otlp/otlplogs" | ||
"github.com/agoda-com/opentelemetry-logs-go/exporters/otlp/otlplogs/otlplogshttp" | ||
"os" | ||
) | ||
|
||
// configure common attributes for all logs | ||
func newResource() *resource.Resource { | ||
hostName, _ := os.Hostname() | ||
return resource.NewWithAttributes( | ||
semconv.SchemaURL, | ||
semconv.ServiceName("otelzap-example"), | ||
semconv.ServiceVersion("1.0.0"), | ||
semconv.HostName(hostName), | ||
) | ||
} | ||
|
||
func main() { | ||
|
||
ctx := context.Background() | ||
|
||
// configure opentelemetry logger provider | ||
logExporter, _ := otlplogs.NewExporter(ctx) | ||
loggerProvider := sdk.NewLoggerProvider( | ||
sdk.WithBatcher(logExporter), | ||
sdk.WithResource(newResource()), | ||
) | ||
// gracefully shutdown logger to flush accumulated signals before program finish | ||
defer loggerProvider.Shutdown(ctx) | ||
|
||
// set opentelemetry logger provider globally | ||
otellogs.SetLoggerProvider(loggerProvider) | ||
|
||
// create new logger with opentelemetry zap core and set it globally | ||
logger := zap.New(otelzap.NewOtelCore(loggerProvider)) | ||
zap.ReplaceGlobals(logger) | ||
|
||
// now your application ready to produce logs to opentelemetry collector | ||
doSomething() | ||
|
||
} | ||
|
||
func doSomething() { | ||
// start new span | ||
// see official trace documentation https://github.com/open-telemetry/opentelemetry-go | ||
tracer := otel.Tracer("my-tracer") | ||
spanCtx, span := tracer.Start(context.Background(), "My Span") | ||
defer func() { | ||
span.End() | ||
}() | ||
|
||
// send log with opentelemetry context | ||
otelzap.Ctx(spanCtx).Info("My message with trace context") | ||
} | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
/* | ||
Copyright Agoda Services Co.,Ltd. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package otelzap | ||
|
||
import ( | ||
otel "github.com/agoda-com/opentelemetry-logs-go/logs" | ||
"go.opentelemetry.io/otel/attribute" | ||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0" | ||
"go.uber.org/zap/zapcore" | ||
"math" | ||
) | ||
|
||
// otelLevel zap level to otlp level converter | ||
func otelLevel(level zapcore.Level) otel.SeverityNumber { | ||
switch level { | ||
case zapcore.DebugLevel: | ||
return otel.DEBUG | ||
case zapcore.InfoLevel: | ||
return otel.INFO | ||
case zapcore.WarnLevel: | ||
return otel.WARN | ||
case zapcore.ErrorLevel: | ||
return otel.ERROR | ||
case zapcore.DPanicLevel: | ||
return otel.ERROR | ||
case zapcore.PanicLevel: | ||
return otel.ERROR | ||
case zapcore.FatalLevel: | ||
return otel.FATAL | ||
} | ||
return otel.TRACE | ||
} | ||
|
||
// otelAttribute convert zap Field into OpenTelemetry Attribute | ||
func otelAttribute(f zapcore.Field) []attribute.KeyValue { | ||
switch f.Type { | ||
case zapcore.UnknownType: | ||
return []attribute.KeyValue{attribute.String(f.Key, f.String)} | ||
case zapcore.BoolType: | ||
return []attribute.KeyValue{attribute.Bool(f.Key, f.Integer == 1)} | ||
case zapcore.Float64Type: | ||
return []attribute.KeyValue{attribute.Float64(f.Key, math.Float64frombits(uint64(f.Integer)))} | ||
case zapcore.Float32Type: | ||
return []attribute.KeyValue{attribute.Float64(f.Key, math.Float64frombits(uint64(f.Integer)))} | ||
case zapcore.Int64Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, f.Integer)} | ||
case zapcore.Int32Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, f.Integer)} | ||
case zapcore.Int16Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, f.Integer)} | ||
case zapcore.Int8Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, f.Integer)} | ||
case zapcore.StringType: | ||
return []attribute.KeyValue{attribute.String(f.Key, f.String)} | ||
case zapcore.Uint64Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, int64(uint64(f.Integer)))} | ||
case zapcore.Uint32Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, int64(uint64(f.Integer)))} | ||
case zapcore.Uint16Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, int64(uint64(f.Integer)))} | ||
case zapcore.Uint8Type: | ||
return []attribute.KeyValue{attribute.Int64(f.Key, int64(uint64(f.Integer)))} | ||
case zapcore.ErrorType: | ||
err := f.Interface.(error) | ||
if err != nil { | ||
return []attribute.KeyValue{semconv.ExceptionMessage(err.Error())} | ||
} | ||
return []attribute.KeyValue{} | ||
case zapcore.SkipType: | ||
return []attribute.KeyValue{} | ||
} | ||
// unhandled types will be treated as string | ||
return []attribute.KeyValue{attribute.String(f.Key, f.String)} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
/* | ||
Copyright Agoda Services Co.,Ltd. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package otelzap | ||
|
||
import ( | ||
otel "github.com/agoda-com/opentelemetry-logs-go/logs" | ||
"go.opentelemetry.io/otel/attribute" | ||
"go.opentelemetry.io/otel/sdk/instrumentation" | ||
semconv "go.opentelemetry.io/otel/semconv/v1.20.0" | ||
"go.opentelemetry.io/otel/trace" | ||
"go.uber.org/zap/zapcore" | ||
) | ||
|
||
const ( | ||
instrumentationName = "github.com/agoda-com/opentelemetry-go/otelzap" | ||
) | ||
|
||
// This class provide interface for OTLP logger | ||
type otlpCore struct { | ||
logger otel.Logger | ||
|
||
fields []zapcore.Field | ||
} | ||
|
||
var instrumentationScope = instrumentation.Scope{ | ||
Name: instrumentationName, | ||
Version: Version(), | ||
SchemaURL: semconv.SchemaURL, | ||
} | ||
|
||
func (otlpCore) Enabled(zapcore.Level) bool { | ||
return true | ||
} | ||
|
||
func (c *otlpCore) With(f []zapcore.Field) zapcore.Core { | ||
fields := c.fields | ||
fields = append(fields, f...) | ||
|
||
return &otlpCore{ | ||
logger: c.logger, | ||
fields: fields, | ||
} | ||
} | ||
|
||
// Check OTLP zap extension method to check if logger is enabled | ||
func (c *otlpCore) Check(entry zapcore.Entry, checked *zapcore.CheckedEntry) *zapcore.CheckedEntry { | ||
if c.Enabled(entry.Level) { | ||
return checked.AddCore(entry, c) | ||
} | ||
return checked | ||
} | ||
|
||
func (c *otlpCore) Sync() error { | ||
return nil | ||
} | ||
|
||
func (c *otlpCore) Write(ent zapcore.Entry, fields []zapcore.Field) error { | ||
var attributes []attribute.KeyValue | ||
var spanCtx *trace.SpanContext | ||
|
||
// add common zap log fields as attributes | ||
for _, s := range c.fields { | ||
if s.Key == "context" { | ||
if ctxValue, ok := s.Interface.(trace.SpanContext); ok { | ||
spanCtx = &ctxValue | ||
} | ||
} else { | ||
attributes = append(attributes, otelAttribute(s)...) | ||
} | ||
} | ||
// add zap log fields as attributes | ||
for _, s := range fields { | ||
attributes = append(attributes, otelAttribute(s)...) | ||
} | ||
|
||
if ent.Level > zapcore.InfoLevel { | ||
callerString := ent.Caller.String() | ||
|
||
if len(callerString) > 0 { | ||
attributes = append(attributes, semconv.ExceptionType(callerString)) | ||
} | ||
|
||
if len(ent.Stack) > 0 { | ||
attributes = append(attributes, semconv.ExceptionStacktrace(ent.Stack)) | ||
} | ||
} | ||
|
||
severityString := ent.Level.String() | ||
severity := otelLevel(ent.Level) | ||
|
||
var traceID *trace.TraceID = nil | ||
var spanID *trace.SpanID = nil | ||
var traceFlags *trace.TraceFlags = nil | ||
if spanCtx != nil { | ||
tid := spanCtx.TraceID() | ||
sid := spanCtx.SpanID() | ||
tf := spanCtx.TraceFlags() | ||
traceID = &tid | ||
spanID = &sid | ||
traceFlags = &tf | ||
} | ||
|
||
lrc := otel.LogRecordConfig{ | ||
Timestamp: &ent.Time, | ||
ObservedTimestamp: ent.Time, | ||
TraceId: traceID, | ||
SpanId: spanID, | ||
TraceFlags: traceFlags, | ||
SeverityText: &severityString, | ||
SeverityNumber: &severity, | ||
Body: &ent.Message, | ||
Resource: nil, | ||
InstrumentationScope: &instrumentationScope, | ||
Attributes: &attributes, | ||
} | ||
|
||
r := otel.NewLogRecord(lrc) | ||
c.logger.Emit(r) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
Copyright Agoda Services Co.,Ltd. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package otelzap | ||
|
||
import ( | ||
"context" | ||
"go.uber.org/zap" | ||
) | ||
|
||
// L returns the global Logger | ||
func L() *Logger { | ||
return &Logger{ | ||
zap.L(), | ||
} | ||
} | ||
|
||
func S() *SugaredLogger { | ||
return L().Sugar() | ||
} | ||
|
||
// Ctx is a shortcut for L().Ctx(ctx). | ||
func Ctx(ctx context.Context) *Logger { | ||
return L().Ctx(ctx) | ||
} |
Oops, something went wrong.