app/main.go (108 lines of code) (raw):
// Copyright 2023 Google LLC
//
// 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
//
// https://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 main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/metric/instrument"
"go.opentelemetry.io/otel/propagation"
"go.opentelemetry.io/otel/sdk/metric"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
"go.opentelemetry.io/otel/trace"
)
// Create channel to listen for signals.
var signalChan chan (os.Signal) = make(chan os.Signal, 1)
var counter instrument.Int64Counter
func main() {
ctx := context.Background()
// set up traceExporter
traceExporter, err := otlptrace.New(ctx,
otlptracegrpc.NewClient(otlptracegrpc.WithInsecure()),
)
if err != nil {
log.Fatalf("Error creating trace exporter: %s", err)
}
// set up tracerprovider
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(sdktrace.AlwaysSample()),
sdktrace.WithSpanProcessor(sdktrace.NewBatchSpanProcessor(traceExporter)),
)
defer tp.Shutdown(ctx)
otel.SetTracerProvider(tp)
otel.SetTextMapPropagator(propagation.TraceContext{})
// set up metrics exporter and meter provider
exporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithInsecure(),
)
if err != nil {
log.Fatalf("Error creating exporter: %s", err)
}
provider := metric.NewMeterProvider(
metric.WithReader(metric.NewPeriodicReader(exporter)),
)
defer provider.Shutdown(ctx)
meter := provider.Meter("example.com/metrics")
counter, err = meter.Int64Counter("sidecar-sample-counter")
if err != nil {
log.Fatalf("Error creating counter: %s", err)
}
// SIGINT handles Ctrl+C locally.
// SIGTERM handles Cloud Run termination signal.
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
// Start HTTP server.
srv := &http.Server{
Addr: ":8080",
Handler: http.HandlerFunc(handler),
}
go func() {
http.HandleFunc("/", handler)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// Receive output from signalChan.
sig := <-signalChan
log.Printf("%s signal caught. Graceful Shutdown.", sig)
// Gracefully shutdown the server by waiting on existing requests (except websockets).
if err := srv.Shutdown(ctx); err != nil {
log.Printf("server shutdown failed: %+v", err)
}
log.Print("server exited")
}
func traceLogPrefix(traceId, spanId string) string {
return fmt.Sprintf("sample-app [%s][spanId: %s]: ", traceId, spanId)
}
func handler(w http.ResponseWriter, r *http.Request) {
// get trace context propagated from http request
prop := otel.GetTextMapPropagator()
ctx := prop.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
tp := otel.GetTracerProvider()
tracer := tp.Tracer("example.com/trace")
ctx, span := tracer.Start(ctx, "foo")
defer span.End()
// extract current span ID
spanId := span.SpanContext().SpanID().String()
traceId := span.SpanContext().TraceID().String()
// open logging file
f, err := os.OpenFile("/logging/sample-app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatalf("Error opening log file: %s", err)
}
defer f.Close()
// log incoming request with spanID
logger := log.New(f, traceLogPrefix(traceId, spanId), log.LstdFlags)
logger.Printf("Request: %s %s", r.Method, r.URL.Path)
fmt.Fprintln(w, "Logged request to /logging/sample-app.log")
// write traces
generateSpans(ctx, tracer, logger, 10)
fmt.Fprintln(w, "Generated 10 spans!")
// update metric
counter.Add(ctx, 100)
fmt.Fprintln(w, "Updated sidecar-sample-counter metric!")
}
func generateSpans(ctx context.Context, tracer trace.Tracer, logger *log.Logger, id int) {
if id > 0 {
ctx, span := tracer.Start(ctx, fmt.Sprintf("foo-%d", id))
defer span.End()
logger.SetPrefix(traceLogPrefix(
span.SpanContext().TraceID().String(),
span.SpanContext().SpanID().String(),
))
logger.Printf("Generating span %d...\n", id)
generateSpans(ctx, tracer, logger, id-1)
} else {
fmt.Println("Done.")
}
}