GKE Structured log with in go with glog and logsrus

2021-01-29

JSON logging on GKE with various golang logging libraries.

This is just a simple http application which emits JSON strings to stdout/stderr from go using

For each log type, there is an endpoint to invoke them where the various handler will emit a JSON string:

{
  "str": "foo",
  "num": 100,
  "bool": false,
  "null": null,
  "array": [
    "foo",
    "bar",
    "baz"
  ],
  "obj": {
    "a": 1,
    "b": 2
  }
}

References:

Using logging for your apps running on Kubernetes Engine

Setup

gcloud container  clusters create cluster-1 --machine-type "n1-standard-2" \
   --zone us-central1-a  \
   --num-nodes 2 --enable-ip-alias  \
   --cluster-version "1.19"
$ kubectl apply -f my-deployment.yaml -f my-srv.yaml
$ kubectl get po,svc
NAME                                    READY   STATUS    RESTARTS   AGE
pod/myapp-deployment-6fcc7f9df9-ddls6   1/1     Running   0          81s
NAME                   TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)          AGE
service/kubernetes     ClusterIP      10.10.32.1     <none>           443/TCP          6d11h
service/myapp-srv-lb   LoadBalancer   10.10.43.107   34.122.242.147   8080:30970/TCP   81s

Once a network loadbalancer is setup, configure a firewall rule to allow you to access te service

gcloud compute  firewall-rules create allow-lb-requests \
  --allow=tcp:8080 --network=default  --source-ranges=0.0.0.0/0

Then invoke each endpoint

export L4=`kubectl get service/myapp-srv-lb -o jsonpath='{.status.loadBalancer.ingress[0].ip}'`
echo $L4

curl -s http://$L4:8080/fmt
curl -s http://$L4:8080/log
curl -s http://$L4:8080/glog
curl -s http://$L4:8080/logsrus

fmt

Creates JSON and parses it as structured:

images/fmt.png

log

Creates JSON and parses it as structured:

images/log.png

glog

Creates JSON and but does NOT parse the inner json:

images/glog.png

logsrus

Creates JSON and but does NOT parse the inner json:

images/logsrus.png


  • server
package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"

	"log"

	"github.com/golang/glog"

	"github.com/sirupsen/logrus"

	"github.com/gorilla/mux"
	"golang.org/x/net/http2"
)

var (
	msg = flag.String("msg", `{"str": "foo","num": 100,"bool": false,"null": null,"array": ["foo", "bar", "baz"],"obj": { "a": 1, "b": 2 } }`, "message to print")
)

const ()

func defaulthandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "ok")
}

func fmtHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(os.Stdout, "%s\n", *msg)
	fmt.Fprint(w, "ok")
}

func logHandler(w http.ResponseWriter, r *http.Request) {
	log.SetFlags(0)
	log.Printf("%s", *msg)
	fmt.Fprint(w, "ok")
}

func glogHandler(w http.ResponseWriter, r *http.Request) {
	glog.Infof("%s", *msg)
	fmt.Fprint(w, "ok")
}

func logsRUsHandler(w http.ResponseWriter, r *http.Request) {
	logrus.SetFormatter(&logrus.JSONFormatter{
		DisableTimestamp: true,
		FieldMap: logrus.FieldMap{
			logrus.FieldKeyLevel: "severity",
		},
	})
	logrus.SetOutput(os.Stdout)
	logrus.SetLevel(logrus.InfoLevel)
	logrus.Infof("%s", *msg)
	fmt.Fprint(w, "ok")
}

func main() {
	flag.Set("stderrthreshold", "INFO")
	flag.Parse()

	router := mux.NewRouter()
	router.Methods(http.MethodGet).Path("/").HandlerFunc(defaulthandler)
	router.Methods(http.MethodGet).Path("/fmt").HandlerFunc(fmtHandler)
	router.Methods(http.MethodGet).Path("/log").HandlerFunc(logHandler)
	router.Methods(http.MethodGet).Path("/glog").HandlerFunc(glogHandler)
	router.Methods(http.MethodGet).Path("/logsrus").HandlerFunc(logsRUsHandler)
	var server *http.Server
	server = &http.Server{
		Addr:    ":8080",
		Handler: router,
	}
	http2.ConfigureServer(server, &http2.Server{})
	log.Printf("Starting Server..")
	err := server.ListenAndServe()
	log.Fatal("Unable to start Server %v", err)

}
  • Dockerfile
FROM golang:1.13 as build

ENV GO111MODULE=on

WORKDIR /app
COPY . .

RUN go mod download

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build server.go

FROM gcr.io/distroless/base
COPY --from=build /app/server /

EXPOSE 8080

ENTRYPOINT ["/server"]
  • deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-deployment
  labels:
    type: myapp-deployment-label
spec:
  replicas: 1
  selector:
    matchLabels:
      type: myapp
  template:
    metadata:
      labels:
        type: myapp
    spec:
      containers:
      - name: frontend
        image: salrashid123/gke_logs
        imagePullPolicy: Always
        ports:
        - containerPort: 8080
          protocol: TCP
          
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-srv-lb
  labels:
    type: myapp-srv-lb
  annotations:
    cloud.google.com/app-protocols: '{"fe":"HTTP"}'
spec:
  type: LoadBalancer  
  ports:
  - name: fe
    port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    type: myapp

This site supports webmentions. Send me a mention via this form.