├── .gitignore ├── .env ├── kong-http-log-server-diagram.png ├── .dockerignore ├── go.mod ├── Dockerfile ├── main.go ├── handler.go ├── README.md ├── docker-compose.yml ├── go.sum └── kong-http-log-server-diagram.drawio /.gitignore: -------------------------------------------------------------------------------- 1 | pg_data 2 | kong-http-log-server 3 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | HOST=0.0.0.0 2 | PORT=8080 3 | ES_HOST=elasticsearch 4 | ES_PORT=9200 5 | INDEX_PATTERN=kong-2006-01-02 6 | -------------------------------------------------------------------------------- /kong-http-log-server-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/domecloud/kong-http-log-server/HEAD/kong-http-log-server-diagram.png -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | Dockerfile 2 | README.md 3 | docker-compose.yml 4 | kong-http-log-server-diagram.drawio 5 | kong-http-log-server-diagram.png 6 | pg_data 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dome.cloud/kong-http-log-server 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 7 | github.com/joho/godotenv v1.3.0 8 | github.com/labstack/echo v3.3.10+incompatible 9 | github.com/labstack/gommon v0.3.0 10 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 // indirect 11 | ) 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.14-alpine as builder 2 | LABEL maintainer="Narate Ketram " 3 | RUN apk --update --no-cache add build-base 4 | WORKDIR /app 5 | ADD . . 6 | RUN go mod download 7 | RUN GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /app/kong-http-log-server . 8 | 9 | FROM alpine 10 | WORKDIR /app 11 | COPY --from=builder /app/kong-http-log-server . 12 | EXPOSE 8080 13 | ENTRYPOINT ["/app/kong-http-log-server"] 14 | 15 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "os/signal" 8 | "time" 9 | 10 | "github.com/joho/godotenv" 11 | "github.com/labstack/echo" 12 | "github.com/labstack/echo/middleware" 13 | "github.com/labstack/gommon/log" 14 | ) 15 | 16 | func main() { 17 | err := godotenv.Load() 18 | if err != nil { 19 | log.Fatal("Error loading .env file") 20 | } 21 | 22 | host := os.Getenv("HOST") 23 | 24 | if host == "" { 25 | host = "0.0.0.0" 26 | } 27 | 28 | port := os.Getenv("PORT") 29 | 30 | if port == "" { 31 | port = "8080" 32 | } 33 | 34 | addr := fmt.Sprintf("%s:%s", host, port) 35 | 36 | e := echo.New() 37 | e.POST("/", ESLogger) 38 | e.Use( 39 | middleware.Recover(), 40 | middleware.Logger(), 41 | middleware.RequestID(), 42 | ) 43 | 44 | // Start server 45 | go func() { 46 | if err := e.Start(addr); err != nil { 47 | e.Logger.Info("shutting down the server") 48 | } 49 | }() 50 | 51 | // Wait for interrupt signal to gracefully shutdown the server with 52 | // a timeout of 10 seconds. 53 | quit := make(chan os.Signal) 54 | signal.Notify(quit, os.Interrupt) 55 | <-quit 56 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 57 | defer cancel() 58 | if err := e.Shutdown(ctx); err != nil { 59 | e.Logger.Fatal(err) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /handler.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "net/http" 8 | "os" 9 | "time" 10 | 11 | "github.com/labstack/echo" 12 | ) 13 | 14 | // ESLogger function for Elastic Search log handler 15 | func ESLogger(c echo.Context) error { 16 | body := echo.Map{} 17 | if err := c.Bind(&body); err != nil { 18 | return err 19 | } 20 | 21 | body["@timestamp"] = time.Now().Format(time.RFC3339) 22 | 23 | indexPattern := os.Getenv("INDEX_PATTERN") 24 | 25 | if indexPattern == "" { 26 | indexPattern = "kong-2006-01-02" 27 | } 28 | 29 | currentTime := time.Now().Format(indexPattern) 30 | 31 | esHost := os.Getenv("ES_HOST") 32 | if esHost == "" { 33 | esHost = "127.0.0.1" 34 | } 35 | 36 | esPort := os.Getenv("ES_PORT") 37 | if esPort == "" { 38 | esPort = "9200" 39 | } 40 | 41 | tr := &http.Transport{} 42 | client := &http.Client{Transport: tr} 43 | 44 | reqBody, err := json.Marshal(body) 45 | 46 | // Send log to ElasticSearch 47 | resp, err := client.Post( 48 | fmt.Sprintf("http://%s:%s/%s/_doc", esHost, esPort, currentTime), 49 | "application/json", 50 | bytes.NewBuffer(reqBody), 51 | ) 52 | 53 | if err != nil { 54 | return err 55 | } 56 | 57 | defer resp.Body.Close() 58 | 59 | var result map[string]interface{} 60 | json.NewDecoder(resp.Body).Decode(&result) 61 | return c.JSON(http.StatusOK, result) 62 | } 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kong-http-log-server 2 | 3 | HTTP log server for Kong [http-log](https://docs.konghq.com/hub/kong-inc/http-log/) plugin for Elasticsearch and more 4 | 5 | ![kong-http-log-server-diagram](./kong-http-log-server-diagram.png) 6 | 7 | 8 | # Why this project was born? 9 | 10 | In some cases of running kong, we have logging server running but firewall or other policy of infrastructure didn’t allow kong connect to log server via TCP/UDP or Syslog, then kong-http-log-server was born 11 | 12 | # Before start 13 | 14 | Edit .env file 15 | 16 | ``` 17 | HOST=0.0.0.0 18 | PORT=8080 19 | ES_HOST=elasticsearch 20 | ES_PORT=9200 21 | INDEX_PATTERN=kong-2006-01-02 22 | ``` 23 | 24 | **NOTE :** 25 | 26 | - `HOST` and `PORT` for http-es-log-server binding 27 | - `ES_HOST` and `ES_PORT` for Elasticsearch Server 28 | - `INDEX_PATTERN` use Golang date format 29 | 30 | Ref: [https://gobyexample.com/time-formatting-parsing](https://gobyexample.com/time-formatting-parsing) 31 | 32 | 33 | # Start 'em all 34 | 35 | ``` 36 | docker-compose up -d kong-database 37 | docker-compose up migrations 38 | docker-compose up -d kong elasticsearch kibana kong-http-log-server 39 | ``` 40 | 41 | # Create sevice and route 42 | 43 | ``` 44 | curl http://127.0.0.1:8001/services -d name=httpbin -d url=http://httpbin.org 45 | curl http://127.0.0.1:8001/services/httpbin/routes -d name=httpbin -d paths[]=/ 46 | ``` 47 | 48 | # Add http-log plugin 49 | 50 | ``` 51 | curl http://127.0.0.1:8001/services/httpbin/plugins \ 52 | -d name=http-log \ 53 | -d config.http_endpoint=http://kong-http-log-server:8080/ 54 | ``` 55 | 56 | # Kibana 57 | 58 | ``` 59 | http://127.0.0.1:5601 60 | ``` 61 | 62 | # Start kong-http-log-server without docker 63 | 64 | ``` 65 | go run . 66 | # or 67 | go build . 68 | ./kong-http-log-server 69 | ``` 70 | 71 | ## TODO 72 | - [ ] Add [basic auth](https://echo.labstack.com/middleware/basic-auth) support via .env for kong-http-log-server 73 | - [ ] Add [loki](https://grafana.com/oss/loki/) support 74 | - [ ] Add [graylog](https://www.graylog.org/) support 75 | - [ ] Add auth support for log backend 76 | - [ ] Add more protocol support for log backend 77 | - [ ] Syslog 78 | - [ ] TCP 79 | - [ ] UDP 80 | 81 | 82 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | kong-database: 4 | image: postgres:12-alpine 5 | container_name: kong-database 6 | environment: 7 | - POSTGRES_USER=kong 8 | - POSTGRES_DB=kong 9 | - POSTGRES_PASSWORD=kong 10 | volumes: 11 | - ./pg_data:/var/lib/postgresql/data 12 | restart: always 13 | healthcheck: 14 | test: ["CMD-SHELL", "pg_isready -U postgres"] 15 | interval: 10s 16 | timeout: 5s 17 | retries: 5 18 | 19 | migrations: 20 | depends_on: 21 | - kong-database 22 | image: kong 23 | container_name: kong-migrations 24 | environment: 25 | - KONG_DATABASE=postgres 26 | - KONG_PG_HOST=kong-database 27 | - KONG_PG_DATABASE=kong 28 | - KONG_PG_USER=kong 29 | - KONG_PG_PASSWORD=kong 30 | command: kong migrations bootstrap -v 31 | 32 | kong: 33 | depends_on: 34 | - kong-database 35 | image: kong 36 | container_name: kong 37 | restart: always 38 | environment: 39 | - KONG_DATABASE=postgres 40 | - KONG_PG_HOST=kong-database 41 | - KONG_PG_DATABASE=kong 42 | - KONG_PG_USER=kong 43 | - KONG_PG_PASSWORD=kong 44 | - KONG_ADMIN_LISTEN=0.0.0.0:8001 45 | - KONG_NGINX_HTTP_RESOLVER=8.8.8.8 46 | - KONG_PROXY_ERROR_LOG=/dev/stderr 47 | - KONG_ADMIN_ERROR_LOG=/dev/stderr 48 | - KONG_TRUSTED_IPS=0.0.0.0/0,::/0 49 | - KONG_REAL_IP_RECURSIVE=on 50 | ports: 51 | - 80:8000 52 | - 8001:8001 53 | - 443:8443 54 | healthcheck: 55 | test: ["CMD", "wget", "-O-", "http://127.0.0.1:8001/status"] 56 | interval: 5s 57 | timeout: 2s 58 | retries: 15 59 | 60 | kong-http-log-server: 61 | build: . 62 | image: domecloud/kong-http-log-server 63 | restart: always 64 | container_name: kong-http-log-server 65 | volumes: 66 | - ./.env:/app/.env 67 | ports: 68 | - 8080:8080 69 | 70 | elasticsearch: 71 | image: docker.elastic.co/elasticsearch/elasticsearch:7.8.0 72 | environment: 73 | - discovery.type=single-node 74 | ports: 75 | - 9200:9200 76 | 77 | kibana: 78 | image: docker.elastic.co/kibana/kibana:7.8.0 79 | environment: 80 | - ELASTICSEARCH_HOSTS=http://elasticsearch:9200 81 | ports: 82 | - 5601:5601 83 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 3 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 4 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 5 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 6 | github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg= 7 | github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s= 8 | github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0= 9 | github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= 10 | github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU= 11 | github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= 12 | github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= 13 | github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg= 14 | github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= 15 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 18 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 19 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 20 | github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8= 21 | github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= 22 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 23 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899 h1:DZhuSZLsGlFL4CmhA8BcRA0mnthyA/nZ00AqCUo7vHg= 24 | golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 25 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= 26 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 27 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 29 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 31 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 32 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 33 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 34 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 35 | -------------------------------------------------------------------------------- /kong-http-log-server-diagram.drawio: -------------------------------------------------------------------------------- 1 | 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 --------------------------------------------------------------------------------