Skip to content

Commit

Permalink
move otelzap from agoda-com/otelzap
Browse files Browse the repository at this point in the history
  • Loading branch information
Aleksandr Nekrasov committed Sep 21, 2023
1 parent 67741e2 commit a774730
Show file tree
Hide file tree
Showing 11 changed files with 528 additions and 1 deletion.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ Open-telemetry extensions for go language

| Logger | Version |
|----------------------|---------|
| [otelslog](otelslog) | v0.0.1 |
| [otelslog](otelslog) | v0.0.1 |
| [otelzap](otelzap) | v0.1.1 |
35 changes: 35 additions & 0 deletions otelzap/CHANGELOG
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

77 changes: 77 additions & 0 deletions otelzap/README.md
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")
}

```
88 changes: 88 additions & 0 deletions otelzap/conv.go
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)}
}
134 changes: 134 additions & 0 deletions otelzap/core.go
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
}
38 changes: 38 additions & 0 deletions otelzap/global.go
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)
}
Loading

0 comments on commit a774730

Please sign in to comment.