Git Product home page Git Product logo

observer's Introduction

Go Doc Build Status Go Report Card Test Coverage Maintainability

observer

This package can be used for building observable applications in Go. It aims to unify three pillars of observability in one single package that is easy-to-use and hard-to-misuse. This package leverages the OpenTelemetry API in an opinionated way.

An Observer encompasses a logger, a meter, and a tracer. It offers a single unified developer experience for enabling observability.

The Three Pillars of Observability

Logging

Logs are used for auditing purposes (sometimes for debugging with limited capabilities). When looking at logs, you need to know what to look for ahead of the time (known unknowns vs. unknown unknowns). Since log data can have any arbitrary shape and size, they cannot be used for real-time computational purposes. Logs are hard to track across different and distributed processes. Logs are also very expensive at scale.

Metrics

Metrics are regular time-series data with low and fixed cardinality. They are aggregated by time. Metrics are used for real-time monitoring purposes. Using metrics with can implement SLIs (service-level indicators), SLOs (service-level objectives), and automated alerts. Metrics are very good at taking the distribution of data into account. Metrics cannot be used with high-cardinality data.

Tracing

Traces are used for debugging and tracking requests across different processes and services. They can be used for identifying performance bottlenecks. Due to their very data-heavy nature, traces in real-world applications need to be sampled. Insights extracted from traces cannot be aggregated since they are sampled. In other words, information captured by one trace does not tell anything about how this trace is compared against other traces, and what is the distribution of data.

Quick Start

Example
package main

import (
  "context"
  "log"
  "net/http"
  "runtime"
  "time"

  "github.com/moorara/observer"
  "go.opentelemetry.io/otel/api/correlation"
  "go.opentelemetry.io/otel/api/kv"
  "go.opentelemetry.io/otel/api/metric"
  "go.uber.org/zap"
)

type instruments struct {
  reqCounter   metric.Int64Counter
  reqDuration  metric.Float64ValueRecorder
  allocatedMem metric.Int64ValueObserver
}

func newInstruments(meter metric.Meter) *instruments {
  mm := metric.Must(meter)

  callback := func(ctx context.Context, result metric.Int64ObserverResult) {
    ms := new(runtime.MemStats)
    runtime.ReadMemStats(ms)
    result.Observe(int64(ms.Alloc),
      kv.String("function", "ReadMemStats"),
    )
  }

  return &instruments{
    reqCounter:   mm.NewInt64Counter("requests_total", metric.WithDescription("the total number of requests")),
    reqDuration:  mm.NewFloat64ValueRecorder("request_duration_seconds", metric.WithDescription("the duration of requests in seconds")),
    allocatedMem: mm.NewInt64ValueObserver("allocated_memory_bytes", callback, metric.WithDescription("number of bytes allocated and in use")),
  }
}

type server struct {
  observer    observer.Observer
  instruments *instruments
}

func (s *server) Handle(ctx context.Context) {
  // Tracing
  ctx, span := s.observer.Tracer().Start(ctx, "handle-request")
  defer span.End()

  start := time.Now()
  s.fetch(ctx)
  s.respond(ctx)
  duration := time.Now().Sub(start)

  labels := []kv.KeyValue{
    kv.String("method", "GET"),
    kv.String("endpoint", "/user"),
    kv.Uint("statusCode", 200),
  }

  // Metrics
  s.observer.Meter().RecordBatch(ctx, labels,
    s.instruments.reqCounter.Measurement(1),
    s.instruments.reqDuration.Measurement(duration.Seconds()),
  )

  // Logging
  s.observer.Logger().Info("request handled successfully.",
    zap.String("method", "GET"),
    zap.String("endpoint", "/user"),
    zap.Uint("statusCode", 200),
  )
}

func (s *server) fetch(ctx context.Context) {
  _, span := s.observer.Tracer().Start(ctx, "read-database")
  defer span.End()

  time.Sleep(50 * time.Millisecond)
}

func (s *server) respond(ctx context.Context) {
  _, span := s.observer.Tracer().Start(ctx, "send-response")
  defer span.End()

  time.Sleep(10 * time.Millisecond)
}

func main() {
  // Creating a new Observer and set it as the singleton
  obsv := observer.New(true, observer.Options{
    Name:        "my-service",
    Version:     "0.1.0",
    Environment: "production",
    Region:      "ca-central-1",
    Tags: map[string]string{
      "domain": "auth",
    },
    LogLevel:            "info",
    JaegerAgentEndpoint: "localhost:6831",
  })
  defer obsv.Close()

  srv := &server{
    observer:    obsv,
    instruments: newInstruments(obsv.Meter()),
  }

  // Creating a correlation context
  ctx := context.Background()
  ctx = correlation.NewContext(ctx,
    kv.String("tenant", "1234"),
  )

  srv.Handle(ctx)

  // Serving metrics endpoint
  http.Handle("/metrics", obsv)
  log.Fatal(http.ListenAndServe(":8080", nil))
}

Here are the logs from stdout:

{"level":"info","timestamp":"2020-06-18T14:15:05.006557-04:00","caller":"example/main.go:69","message":"request handled successfully.","domain":"auth","environment":"production","logger":"my-service","region":"ca-central-1","version":"0.1.0","method":"GET","endpoint":"/user","statusCode":200}

And here are the metrics reported at http://localhost:8080/metrics:

# HELP allocated_memory_bytes number of bytes allocated and in use
# TYPE allocated_memory_bytes histogram
allocated_memory_bytes_bucket{function="ReadMemStats",le="+Inf"} 2
allocated_memory_bytes_sum{function="ReadMemStats"} 2.454424e+06
allocated_memory_bytes_count{function="ReadMemStats"} 2
# HELP request_duration_seconds the duration of requests in seconds
# TYPE request_duration_seconds histogram
request_duration_seconds_bucket{endpoint="/user",method="GET",statusCode="200",le="+Inf"} 1
request_duration_seconds_sum{endpoint="/user",method="GET",statusCode="200"} 0.065625226
request_duration_seconds_count{endpoint="/user",method="GET",statusCode="200"} 1
# HELP requests_total the total number of requests
# TYPE requests_total counter
requests_total{endpoint="/user",method="GET",statusCode="200"} 1

OpenTelemetry

Logging

TBD

Metrics

Metric instruments capture measurements at runtime. A Meter is used for creating metric instruments.

There are two kinds of measurements:

  • Additive: measurements for which only the sum is considered useful information
  • Non-Additive: measurements for which the set of values (a.k.a. population or distribution) has useful information

Non-additive instruments capture more information than additive instruments, but non-additive measurements are more expensive.

Aggregation is the process of combining multiple measurements into exact or estimated statistics during an interval of time. Each instrument has a default aggregation. Other standard aggregations (histograms, quantile summaries, cardinality estimates, etc.) are also available.

There are six kinds of metric instruments:

Name Synchronous Additive Monotonic Default Aggregation
Counter Yes Yes Yes Sum
UpDownCounter Yes Yes No Sum
ValueRecorder Yes No No MinMaxSumCount
SumObserver No Yes Yes Sum
UpDownSumObserver No Yes No Sum
ValueObserver No No No MinMaxSumCount

The synchronous instruments are useful for measurements that are gathered in a distributed Context. The asynchronous instruments are useful when measurements are expensive, therefore should be gathered periodically. Synchronous instruments are used to capture changes in a sum, whereas asynchronous instruments are used to capture sums directly. Asynchronous (observer) instruments capture measurements about the state of the application periodically.

Tracing

TBD

Documentation

observer's People

Contributors

moorara avatar renovate[bot] avatar

Watchers

 avatar  avatar

Forkers

arcano-dev

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.