all repos

onasty @ ecdd5a0f7bd8d3a7948aad8aec355e56acbe07d9

a one-time notes service
20 files changed, 320 insertions(+), 24 deletions(-)
feat: metrics (#24)

* feat: setup prometheus handler in app

* chore: setup prometheus service

* go mod tidy

* feat(metrics): add custom_http_metrics

* feat(http): add middleware to record http metrics

* feat(metrics): record http requests latency

* feat(infra): add grafana

* refactor(logger): add more configuration options

* go mod tidy

* chore(docker): run the app in docker

* chore: setup loki and promtail

* chore(grafana): add datasources

* chore(prometheus): use right address

* fix(transport): count 400 responses as errors, and dont log request_id
twice

* chore(taskfile): build containers on start

* fixup! chore(prometheus): use right address

* refactor(promtail): remove usless pipelines

* refactor(prometheus): update scraping intervals

* Revert "refactor(logger): add more configuration options"

This reverts commit 6fee06063373df746afd88529111eecae0df163c.

* clean up

* chore(loki): dont use alter manager since there's none

* do not set grpc ports

* reformat prometheus config

* refactor: make metrics port consistent with server one

* chore(Taskfile): fix migration commands

* chore(taskfile): update run command

* feat(mail): add migrations

* fix(prometheus): use right port

* chore(docker): fix the tag for core service
Author: Smirnov Oleksandr ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2024-10-05 23:23:51 +0300
Parent: a4b3d8e
M .env.example
···
        1
        1
         APP_ENV=debug

      
        2
        
        -APP_URL=http://localhost:3000

      
        3
        
        -SERVER_PORT=3000

      
        
        2
        +APP_URL=http://localhost:8000

      
        
        3
        +SERVER_PORT=8000

      
        4
        4
         PASSWORD_SALT=onasty

      
        
        5
        +METRICS_PORT=8001

      
        5
        6
         

      
        6
        7
         LOG_LEVEL=debug

      
        7
        8
         LOG_FORMAT=text

      ···
        13
        14
         

      
        14
        15
         POSTGRES_USERNAME=onasty

      
        15
        16
         POSTGRES_PASSWORD=qwerty

      
        16
        
        -POSTGRES_HOST=127.0.0.1

      
        
        17
        +POSTGRES_HOST=postgres

      
        17
        18
         POSTGRES_PORT=5432

      
        18
        19
         POSTGRES_DATABASE=onasty

      
        19
        20
         POSTGRESQL_DSN="postgres://$POSTGRES_USERNAME:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DATABASE?sslmode=disable"

      
        
        21
        +MIGRATION_DSN="postgres://$POSTGRES_USERNAME:$POSTGRES_PASSWORD@localhost:$POSTGRES_PORT/$POSTGRES_DATABASE?sslmode=disable"

      
        20
        22
         

      
        21
        23
         MAILGUN_FROM=onasty@mail.com

      
        22
        24
         MAILGUN_DOMAI='<domain>'

      
A Dockerfile
···
        
        1
        +FROM golang:1.23.1-alpine AS builder

      
        
        2
        +

      
        
        3
        +WORKDIR /app

      
        
        4
        +

      
        
        5
        +COPY go.mod go.sum ./

      
        
        6
        +RUN go mod download

      
        
        7
        +

      
        
        8
        +COPY cmd cmd

      
        
        9
        +COPY internal internal

      
        
        10
        +

      
        
        11
        +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags='-w -s' -o /onasty ./cmd/server

      
        
        12
        +

      
        
        13
        +FROM scratch

      
        
        14
        +COPY --from=builder /onasty /onasty

      
        
        15
        +ENTRYPOINT ["/onasty"]

      
M Taskfile.yml
···
        11
        11
             - go build -o .bin/onasty ./cmd/server/

      
        12
        12
         

      
        13
        13
           run:

      
        14
        
        -    - task: build

      
        15
        
        -    - .bin/onasty

      
        
        14
        +    - docker compose up -d --build core

      
        16
        15
         

      
        17
        16
           lint:

      
        18
        17
             - golangci-lint run

      
        19
        18
         

      
        20
        19
           docker:up:

      
        21
        
        -    - docker compose up -d

      
        
        20
        +    - docker compose up -d --build --remove-orphans

      
        22
        21
         

      
        23
        22
           docker:down:

      
        24
        23
             aliases: [docker:stop]

      
M cmd/server/main.go
···
        15
        15
         	"github.com/olexsmir/onasty/internal/jwtutil"

      
        16
        16
         	"github.com/olexsmir/onasty/internal/logger"

      
        17
        17
         	"github.com/olexsmir/onasty/internal/mailer"

      
        
        18
        +	"github.com/olexsmir/onasty/internal/metrics"

      
        18
        19
         	"github.com/olexsmir/onasty/internal/service/notesrv"

      
        19
        20
         	"github.com/olexsmir/onasty/internal/service/usersrv"

      
        20
        21
         	"github.com/olexsmir/onasty/internal/store/psql/noterepo"

      ···
        87
        88
         	// http server

      
        88
        89
         	srv := httpserver.NewServer(cfg.ServerPort, handler.Handler())

      
        89
        90
         	go func() {

      
        90
        
        -		slog.Debug("starting http server", "port", cfg.ServerPort)

      
        
        91
        +		slog.Info("starting http server", "port", cfg.ServerPort)

      
        91
        92
         		if err := srv.Start(); !errors.Is(err, http.ErrServerClosed) {

      
        92
        93
         			slog.Error("failed to start http server", "error", err)

      
        93
        94
         		}

      
        94
        95
         	}()

      
        
        96
        +

      
        
        97
        +	// metrics

      
        
        98
        +	if cfg.MetricsEnabled {

      
        
        99
        +		mSrv := httpserver.NewServer(cfg.MetricsPort, metrics.Handler())

      
        
        100
        +		go func() {

      
        
        101
        +			slog.Info("starting metrics server", "port", cfg.MetricsPort)

      
        
        102
        +			if err := mSrv.Start(); !errors.Is(err, http.ErrServerClosed) {

      
        
        103
        +				slog.Error("failed to start metrics server", "error", err)

      
        
        104
        +			}

      
        
        105
        +		}()

      
        
        106
        +	}

      
        95
        107
         

      
        96
        108
         	// graceful shutdown

      
        97
        109
         	quit := make(chan os.Signal, 1)

      
M docker-compose.yml
···
        1
        1
         services:

      
        
        2
        +  core:

      
        
        3
        +    image: onasty:core

      
        
        4
        +    container_name: onasty-core

      
        
        5
        +    build:

      
        
        6
        +      context: .

      
        
        7
        +      dockerfile: Dockerfile

      
        
        8
        +    env_file: .env

      
        
        9
        +    ports:

      
        
        10
        +      - 8000:8000

      
        
        11
        +      - 8001:8001

      
        
        12
        +

      
        2
        13
           postgres:

      
        3
        14
             image: postgres:16-alpine

      
        4
        15
             container_name: onasty-postgres

      ···
        10
        21
               - .docker/postgres:/var/lib/postgresql/data

      
        11
        22
             ports:

      
        12
        23
               - 5432:5432

      
        
        24
        +

      
        
        25
        +  prometheus:

      
        
        26
        +    image: prom/prometheus

      
        
        27
        +    container_name: onasty-prometheus

      
        
        28
        +    user: root

      
        
        29
        +    volumes:

      
        
        30
        +      - ./.docker/prometheus:/prometheus

      
        
        31
        +      - ./infra/prometheus:/etc/prometheus

      
        
        32
        +    ports:

      
        
        33
        +      - 9090:9090

      
        
        34
        +

      
        
        35
        +  grafana:

      
        
        36
        +    image: grafana/grafana:11.1.6

      
        
        37
        +    container_name: onasty-grafana

      
        
        38
        +    user: root

      
        
        39
        +    environment:

      
        
        40
        +      - GF_SECURITY_ADMIN_USER=admin

      
        
        41
        +      - GF_SECURITY_ADMIN_PASSWORD=admin

      
        
        42
        +    volumes:

      
        
        43
        +      - ./.docker/grafana:/var/lib/grafana

      
        
        44
        +      - ./infra/grafana/datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml

      
        
        45
        +    ports:

      
        
        46
        +      - 3069:3000

      
        
        47
        +

      
        
        48
        +  loki:

      
        
        49
        +    image: grafana/loki:3.2.0

      
        
        50
        +    command: ["--pattern-ingester.enabled=true", "-config.file=/etc/loki/config.yaml"]

      
        
        51
        +    ports:

      
        
        52
        +      - 3100:3100

      
        
        53
        +    volumes:

      
        
        54
        +      - ./infra/loki/config.yaml:/etc/loki/config.yaml

      
        
        55
        +

      
        
        56
        +  promtail:

      
        
        57
        +    image: grafana/promtail:3.0.0

      
        
        58
        +    command: -config.file=/etc/promtail/config.yaml

      
        
        59
        +    volumes:

      
        
        60
        +      - /var/run/docker.sock:/var/run/docker.sock

      
        
        61
        +      - ./infra/promtail/config.yaml:/etc/promtail/config.yaml

      
M go.mod
···
        11
        11
         	github.com/jackc/pgx-gofrs-uuid v0.0.0-20230224015001-1d428863c2e2

      
        12
        12
         	github.com/jackc/pgx/v5 v5.7.1

      
        13
        13
         	github.com/mailgun/mailgun-go/v4 v4.16.0

      
        
        14
        +	github.com/prometheus/client_golang v1.20.4

      
        14
        15
         	github.com/stretchr/testify v1.9.0

      
        15
        16
         	github.com/testcontainers/testcontainers-go v0.33.0

      
        16
        17
         	github.com/testcontainers/testcontainers-go/modules/postgres v0.33.0

      ···
        21
        22
         	github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect

      
        22
        23
         	github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect

      
        23
        24
         	github.com/Microsoft/go-winio v0.6.2 // indirect

      
        
        25
        +	github.com/beorn7/perks v1.0.1 // indirect

      
        24
        26
         	github.com/bytedance/sonic v1.11.6 // indirect

      
        25
        27
         	github.com/bytedance/sonic/loader v0.1.1 // indirect

      
        26
        28
         	github.com/cenkalti/backoff/v4 v4.2.1 // indirect

      
        
        29
        +	github.com/cespare/xxhash/v2 v2.3.0 // indirect

      
        27
        30
         	github.com/cloudwego/base64x v0.1.4 // indirect

      
        28
        31
         	github.com/cloudwego/iasm v0.2.0 // indirect

      
        29
        32
         	github.com/containerd/log v0.1.0 // indirect

      ···
        60
        63
         	github.com/jackc/pgx/v4 v4.18.2 // indirect

      
        61
        64
         	github.com/jackc/puddle/v2 v2.2.2 // indirect

      
        62
        65
         	github.com/json-iterator/go v1.1.12 // indirect

      
        63
        
        -	github.com/klauspost/compress v1.17.4 // indirect

      
        
        66
        +	github.com/klauspost/compress v1.17.9 // indirect

      
        64
        67
         	github.com/klauspost/cpuid/v2 v2.2.7 // indirect

      
        65
        68
         	github.com/leodido/go-urn v1.4.0 // indirect

      
        66
        69
         	github.com/lib/pq v1.10.9 // indirect

      ···
        77
        80
         	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect

      
        78
        81
         	github.com/modern-go/reflect2 v1.0.2 // indirect

      
        79
        82
         	github.com/morikuni/aec v1.0.0 // indirect

      
        
        83
        +	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect

      
        80
        84
         	github.com/opencontainers/go-digest v1.0.0 // indirect

      
        81
        85
         	github.com/opencontainers/image-spec v1.1.0 // indirect

      
        82
        86
         	github.com/pelletier/go-toml/v2 v2.2.2 // indirect

      
        83
        87
         	github.com/pkg/errors v0.9.1 // indirect

      
        84
        88
         	github.com/pmezard/go-difflib v1.0.0 // indirect

      
        85
        89
         	github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect

      
        
        90
        +	github.com/prometheus/client_model v0.6.1 // indirect

      
        
        91
        +	github.com/prometheus/common v0.55.0 // indirect

      
        
        92
        +	github.com/prometheus/procfs v0.15.1 // indirect

      
        86
        93
         	github.com/shirou/gopsutil/v3 v3.23.12 // indirect

      
        87
        94
         	github.com/shoenig/go-m1cpu v0.1.6 // indirect

      
        88
        95
         	github.com/sirupsen/logrus v1.9.3 // indirect

      ···
        96
        103
         	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect

      
        97
        104
         	go.opentelemetry.io/otel/metric v1.29.0 // indirect

      
        98
        105
         	go.opentelemetry.io/otel/trace v1.29.0 // indirect

      
        99
        
        -	go.uber.org/atomic v1.7.0 // indirect

      
        
        106
        +	go.uber.org/atomic v1.9.0 // indirect

      
        100
        107
         	golang.org/x/arch v0.8.0 // indirect

      
        101
        108
         	golang.org/x/crypto v0.27.0 // indirect

      
        102
        109
         	golang.org/x/net v0.29.0 // indirect

      
M go.sum
···
        9
        9
         github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=

      
        10
        10
         github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=

      
        11
        11
         github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=

      
        
        12
        +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=

      
        
        13
        +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=

      
        12
        14
         github.com/ahmetb/go-linq v3.0.0+incompatible h1:qQkjjOXKrKOTy83X8OpRmnKflXKQIL/mC/gMVVDMhOA=

      
        13
        15
         github.com/ahmetb/go-linq v3.0.0+incompatible/go.mod h1:PFffvbdbtw+QTB0WKRP0cNht7vnCfnGlEpak/DVg5cY=

      
        14
        16
         github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=

      ···
        17
        19
         github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=

      
        18
        20
         github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=

      
        19
        21
         github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=

      
        
        22
        +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=

      
        
        23
        +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=

      
        20
        24
         github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=

      
        21
        25
         github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=

      
        22
        26
         github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=

      ···
        169
        173
         github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=

      
        170
        174
         github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=

      
        171
        175
         github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=

      
        172
        
        -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4=

      
        173
        
        -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=

      
        
        176
        +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=

      
        
        177
        +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=

      
        174
        178
         github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=

      
        175
        179
         github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=

      
        176
        180
         github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=

      ···
        178
        182
         github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=

      
        179
        183
         github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=

      
        180
        184
         github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=

      
        181
        
        -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=

      
        182
        
        -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=

      
        
        185
        +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=

      
        
        186
        +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=

      
        183
        187
         github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=

      
        184
        188
         github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=

      
        185
        189
         github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=

      
        186
        190
         github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=

      
        187
        191
         github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=

      
        
        192
        +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=

      
        
        193
        +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=

      
        188
        194
         github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=

      
        189
        195
         github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=

      
        190
        196
         github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=

      ···
        227
        233
         github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=

      
        228
        234
         github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=

      
        229
        235
         github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=

      
        
        236
        +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=

      
        
        237
        +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=

      
        230
        238
         github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=

      
        231
        239
         github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=

      
        232
        240
         github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=

      ···
        240
        248
         github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=

      
        241
        249
         github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=

      
        242
        250
         github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=

      
        
        251
        +github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI=

      
        
        252
        +github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=

      
        
        253
        +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=

      
        
        254
        +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=

      
        
        255
        +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=

      
        
        256
        +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=

      
        
        257
        +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=

      
        
        258
        +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=

      
        243
        259
         github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=

      
        244
        260
         github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=

      
        245
        261
         github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=

      ···
        314
        330
         go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=

      
        315
        331
         go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=

      
        316
        332
         go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=

      
        317
        
        -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=

      
        318
        
        -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=

      
        
        333
        +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=

      
        
        334
        +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=

      
        319
        335
         go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=

      
        320
        336
         go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=

      
        321
        337
         go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=

      
A infra/grafana/datasources.yml
···
        
        1
        +apiVersion: 1

      
        
        2
        +datasources:

      
        
        3
        +  - name: loki

      
        
        4
        +    type: loki

      
        
        5
        +    access: proxy

      
        
        6
        +    url: http://loki:3100

      
        
        7
        +    isDefault: false

      
        
        8
        +

      
        
        9
        +  - name: prometheus

      
        
        10
        +    type: prometheus

      
        
        11
        +    access: proxy

      
        
        12
        +    url: http://prometheus:9090

      
        
        13
        +    isDefault: true

      
A infra/loki/config.yaml
···
        
        1
        +auth_enabled: false

      
        
        2
        +

      
        
        3
        +server:

      
        
        4
        +  http_listen_port: 3100

      
        
        5
        +

      
        
        6
        +limits_config:

      
        
        7
        +  allow_structured_metadata: false

      
        
        8
        +

      
        
        9
        +common:

      
        
        10
        +  path_prefix: /tmp/loki

      
        
        11
        +  storage:

      
        
        12
        +    filesystem:

      
        
        13
        +      chunks_directory: /tmp/loki/chunks

      
        
        14
        +      rules_directory: /tmp/loki/rules

      
        
        15
        +  replication_factor: 1

      
        
        16
        +  ring:

      
        
        17
        +    instance_addr: 127.0.0.1

      
        
        18
        +    kvstore:

      
        
        19
        +      store: inmemory

      
        
        20
        +

      
        
        21
        +schema_config:

      
        
        22
        +  configs:

      
        
        23
        +    - from: 2020-10-24

      
        
        24
        +      store: boltdb-shipper

      
        
        25
        +      object_store: filesystem

      
        
        26
        +      schema: v11

      
        
        27
        +      index:

      
        
        28
        +        prefix: index_

      
        
        29
        +        period: 24h

      
A infra/prometheus/prometheus.yml
···
        
        1
        +global:

      
        
        2
        +  scrape_interval: 5s

      
        
        3
        +  scrape_timeout: 2s

      
        
        4
        +  evaluation_interval: 15s

      
        
        5
        +

      
        
        6
        +scrape_configs:

      
        
        7
        +  - job_name: core

      
        
        8
        +    metrics_path: /metrics

      
        
        9
        +    scheme: http

      
        
        10
        +    follow_redirects: true

      
        
        11
        +    honor_timestamps: true

      
        
        12
        +    static_configs:

      
        
        13
        +    - targets: [core:8001]

      
A infra/promtail/config.yaml
···
        
        1
        +server:

      
        
        2
        +  http_listen_port: 9080

      
        
        3
        +

      
        
        4
        +positions:

      
        
        5
        +  filename: /tmp/positions.yaml

      
        
        6
        +

      
        
        7
        +clients:

      
        
        8
        +  - url: http://loki:3100/loki/api/v1/push

      
        
        9
        +

      
        
        10
        +scrape_configs:

      
        
        11
        +  - job_name: containers

      
        
        12
        +    docker_sd_configs:

      
        
        13
        +      - host: unix:///var/run/docker.sock

      
        
        14
        +    relabel_configs:

      
        
        15
        +      - source_labels: [__meta_docker_container_name]

      
        
        16
        +        target_label: container

      
        
        17
        +        regex: '/(.+)'

      
        
        18
        +        replacement: '$1'

      
        
        19
        +      - source_labels: [__meta_docker_container_id]

      
        
        20
        +        target_label: container_id

      
M internal/config/config.go
···
        22
        22
         	MailgunAPIKey        string

      
        23
        23
         	VerificationTokenTTL time.Duration

      
        24
        24
         

      
        
        25
        +	MetricsEnabled bool

      
        
        26
        +	MetricsPort    string

      
        
        27
        +

      
        25
        28
         	LogLevel    string

      
        26
        29
         	LogFormat   string

      
        27
        30
         	LogShowLine bool

      ···
        49
        52
         		VerificationTokenTTL: mustParseDurationOrPanic(

      
        50
        53
         			getenvOrDefault("VERIFICATION_TOKEN_TTL", "24h"),

      
        51
        54
         		),

      
        
        55
        +

      
        
        56
        +		MetricsPort:    getenvOrDefault("METRICS_PORT", "3001"),

      
        
        57
        +		MetricsEnabled: getenvOrDefault("METRICS_ENABLED", "true") == "true",

      
        52
        58
         

      
        53
        59
         		LogLevel:    getenvOrDefault("LOG_LEVEL", "debug"),

      
        54
        60
         		LogFormat:   getenvOrDefault("LOG_FORMAT", "json"),

      
M internal/mailer/mailgun.go
···
        5
        5
         	"log/slog"

      
        6
        6
         

      
        7
        7
         	"github.com/mailgun/mailgun-go/v4"

      
        
        8
        +	"github.com/olexsmir/onasty/internal/metrics"

      
        
        9
        +	"github.com/olexsmir/onasty/internal/transport/http/reqid"

      
        8
        10
         )

      
        9
        11
         

      
        10
        12
         var _ Mailer = (*Mailgun)(nil)

      ···
        27
        29
         	msg := m.mg.NewMessage(m.from, subject, "", to)

      
        28
        30
         	msg.SetHtml(content)

      
        29
        31
         

      
        
        32
        +	slog.InfoContext(ctx, "email sent", "to", to)

      
        
        33
        +

      
        30
        34
         	_, _, err := m.mg.Send(ctx, msg)

      
        
        35
        +	if err != nil {

      
        
        36
        +		metrics.RecordEmailFailed(reqid.GetContext(ctx))

      
        
        37
        +		return err

      
        
        38
        +	}

      
        31
        39
         

      
        32
        
        -	slog.InfoContext(ctx, "email sent", "to", to)

      
        33
        40
         	slog.DebugContext(ctx, "email sent", "subject", subject, "content", content, "err", err)

      
        
        41
        +	metrics.RecordEmailSent()

      
        34
        42
         

      
        35
        
        -	return err

      
        
        43
        +	return nil

      
        36
        44
         }

      
A internal/metrics/http_metrics.go
···
        
        1
        +package metrics

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"time"

      
        
        5
        +

      
        
        6
        +	"github.com/prometheus/client_golang/prometheus"

      
        
        7
        +	"github.com/prometheus/client_golang/prometheus/promauto"

      
        
        8
        +)

      
        
        9
        +

      
        
        10
        +var (

      
        
        11
        +	successfulHTTPRequest = promauto.NewCounterVec(prometheus.CounterOpts{ //nolint:exhaustruct

      
        
        12
        +		Name:        "http_successful_requests_total",

      
        
        13
        +		Help:        "the total number of successful http requests",

      
        
        14
        +		ConstLabels: map[string]string{"status": "success"},

      
        
        15
        +	}, []string{"method", "uri"})

      
        
        16
        +

      
        
        17
        +	failedHTTPRequest = promauto.NewCounterVec(prometheus.CounterOpts{ //nolint:exhaustruct

      
        
        18
        +		Name:        "http_failed_requests_total",

      
        
        19
        +		Help:        "the total number of failed http requests",

      
        
        20
        +		ConstLabels: map[string]string{"status": "failure"},

      
        
        21
        +	}, []string{"method", "uri"})

      
        
        22
        +

      
        
        23
        +	latencyHTTPRequest = promauto.NewHistogramVec(prometheus.HistogramOpts{ //nolint:exhaustruct

      
        
        24
        +		Name:    "http_request_latency_seconds",

      
        
        25
        +		Help:    "the latency of http requests in seconds",

      
        
        26
        +		Buckets: prometheus.DefBuckets,

      
        
        27
        +	}, []string{"method", "uri"})

      
        
        28
        +)

      
        
        29
        +

      
        
        30
        +func RecordSuccessfulRequestMetric(method, uri string) {

      
        
        31
        +	go successfulHTTPRequest.With(prometheus.Labels{

      
        
        32
        +		"method": method,

      
        
        33
        +		"uri":    uri,

      
        
        34
        +	}).Inc()

      
        
        35
        +}

      
        
        36
        +

      
        
        37
        +func RecordFailedRequestMetric(method, uri string) {

      
        
        38
        +	go failedHTTPRequest.With(prometheus.Labels{

      
        
        39
        +		"method": method,

      
        
        40
        +		"uri":    uri,

      
        
        41
        +	}).Inc()

      
        
        42
        +}

      
        
        43
        +

      
        
        44
        +func RecordLatencyRequestMetric(method, uri string, latency time.Duration) {

      
        
        45
        +	go latencyHTTPRequest.With(prometheus.Labels{

      
        
        46
        +		"method": method,

      
        
        47
        +		"uri":    uri,

      
        
        48
        +	}).Observe(latency.Seconds())

      
        
        49
        +}

      
A internal/metrics/mail_metrics.go
···
        
        1
        +package metrics

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"github.com/prometheus/client_golang/prometheus"

      
        
        5
        +	"github.com/prometheus/client_golang/prometheus/promauto"

      
        
        6
        +)

      
        
        7
        +

      
        
        8
        +var (

      
        
        9
        +	emailSentSuccessfully = promauto.NewCounter(prometheus.CounterOpts{ //nolint:exhaustruct

      
        
        10
        +		Name: "mail_sent_total",

      
        
        11
        +		Help: "the total number of successfully sent email",

      
        
        12
        +	})

      
        
        13
        +

      
        
        14
        +	emailFailedToSend = promauto.NewCounterVec(prometheus.CounterOpts{ //nolint:exhaustruct

      
        
        15
        +		Name: "mail_failed_total",

      
        
        16
        +		Help: "the total number of email that failed to send",

      
        
        17
        +	}, []string{"request_id"})

      
        
        18
        +)

      
        
        19
        +

      
        
        20
        +func RecordEmailSent() {

      
        
        21
        +	go emailSentSuccessfully.Inc()

      
        
        22
        +}

      
        
        23
        +

      
        
        24
        +func RecordEmailFailed(reqid string) {

      
        
        25
        +	go emailFailedToSend.With(prometheus.Labels{

      
        
        26
        +		"request_id": reqid,

      
        
        27
        +	}).Inc()

      
        
        28
        +}

      
A internal/metrics/metrics.go
···
        
        1
        +package metrics

      
        
        2
        +

      
        
        3
        +import (

      
        
        4
        +	"net/http"

      
        
        5
        +

      
        
        6
        +	"github.com/prometheus/client_golang/prometheus/promhttp"

      
        
        7
        +)

      
        
        8
        +

      
        
        9
        +func Handler() http.Handler {

      
        
        10
        +	mux := http.NewServeMux()

      
        
        11
        +	mux.Handle("GET /metrics", promhttp.Handler())

      
        
        12
        +	return mux

      
        
        13
        +}

      
M internal/transport/http/apiv1/apiv1.go
···
        22
        22
         }

      
        23
        23
         

      
        24
        24
         func (a *APIV1) Routes(r *gin.RouterGroup) {

      
        
        25
        +	r.Use(a.metricsMiddleware)

      
        25
        26
         	auth := r.Group("/auth")

      
        26
        27
         	{

      
        27
        28
         		auth.POST("/signup", a.signUpHandler)

      
M internal/transport/http/apiv1/middleware.go
···
        3
        3
         import (

      
        4
        4
         	"context"

      
        5
        5
         	"strings"

      
        
        6
        +	"time"

      
        6
        7
         

      
        7
        8
         	"github.com/gin-gonic/gin"

      
        8
        9
         	"github.com/gofrs/uuid/v5"

      
        
        10
        +	"github.com/olexsmir/onasty/internal/metrics"

      
        9
        11
         	"github.com/olexsmir/onasty/internal/models"

      
        10
        12
         )

      
        11
        13
         

      ···
        50
        52
         	}

      
        51
        53
         

      
        52
        54
         	c.Next()

      
        
        55
        +}

      
        
        56
        +

      
        
        57
        +func (a *APIV1) metricsMiddleware(c *gin.Context) {

      
        
        58
        +	start := time.Now()

      
        
        59
        +	c.Next()

      
        
        60
        +	latency := time.Since(start)

      
        
        61
        +

      
        
        62
        +	metrics.RecordLatencyRequestMetric(c.Request.Method, c.Request.RequestURI, latency)

      
        
        63
        +

      
        
        64
        +	if c.Writer.Status() >= 200 && c.Writer.Status() < 300 {

      
        
        65
        +		metrics.RecordSuccessfulRequestMetric(c.Request.Method, c.Request.RequestURI)

      
        
        66
        +	}

      
        
        67
        +

      
        
        68
        +	if c.Writer.Status() >= 400 {

      
        
        69
        +		metrics.RecordFailedRequestMetric(c.Request.Method, c.Request.RequestURI)

      
        
        70
        +	}

      
        53
        71
         }

      
        54
        72
         

      
        55
        73
         //nolint:unused // TODO: remove me later

      
M internal/transport/http/middlewares.go
···
        5
        5
         	"time"

      
        6
        6
         

      
        7
        7
         	"github.com/gin-gonic/gin"

      
        8
        
        -	"github.com/olexsmir/onasty/internal/transport/http/reqid"

      
        9
        8
         )

      
        10
        9
         

      
        11
        10
         func (t *Transport) logger() gin.HandlerFunc {

      ···
        22
        21
         		}

      
        23
        22
         

      
        24
        23
         		lvl := slog.LevelInfo

      
        25
        
        -		if c.Writer.Status() >= 500 {

      
        
        24
        +		if c.Writer.Status() >= 400 {

      
        26
        25
         			lvl = slog.LevelError

      
        27
        26
         		}

      
        28
        27
         

      ···
        30
        29
         			c.Request.Context(),

      
        31
        30
         			lvl,

      
        32
        31
         			c.Errors.ByType(gin.ErrorTypePrivate).String(),

      
        33
        
        -			slog.String("request_id", reqid.Get(c)),

      
        34
        32
         			slog.String("latency", latency.String()),

      
        35
        33
         			slog.String("method", c.Request.Method),

      
        36
        34
         			slog.Int("status_code", c.Writer.Status()),

      
M migrations/Taskfile.yml
···
        10
        10
               - migrate create -ext sql -dir {{.MIGRATIONS_DIR}} {{ .CLI_ARGS }}

      
        11
        11
         

      
        12
        12
           up:

      
        13
        
        -    - migrate -database $POSTGRESQL_DSN  -path {{.MIGRATIONS_DIR}} up

      
        
        13
        +    - migrate -database $MIGRATION_DSN -path {{.MIGRATIONS_DIR}} up

      
        14
        14
         

      
        15
        15
           down:

      
        16
        
        -    - migrate -database $POSTGRESQL_DSN -path {{.MIGRATIONS_DIR}} down 1

      
        
        16
        +    - migrate -database $MIGRATION_DSN -path {{.MIGRATIONS_DIR}} down 1

      
        17
        17
         

      
        18
        18
           drop:

      
        19
        
        -    - migrate -database $POSTGRESQL_DSN -path {{.MIGRATIONS_DIR}} drop

      
        
        19
        +    - migrate -database $MIGRATION_DSN -path {{.MIGRATIONS_DIR}} drop

      
        20
        20
         

      
        21
        21
           current-version:

      
        22
        
        -    - migrate -database $POSTGRESQL_DSN -path {{.MIGRATIONS_DIR}} version

      
        
        22
        +    - migrate -database $MIGRATION_DSN -path {{.MIGRATIONS_DIR}} version