Skip to content

Commit

Permalink
87 differentiate between false and true verifications (#260)
Browse files Browse the repository at this point in the history
* fix(logging): use axiom again for logs

* feat(ui): differentiate between success or denied verifications

resolves #87
  • Loading branch information
chronark authored Sep 16, 2023
1 parent d1a3223 commit 1893a0a
Show file tree
Hide file tree
Showing 16 changed files with 347 additions and 83 deletions.
30 changes: 24 additions & 6 deletions apps/agent/cmd/agent/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package agent
import (
"context"
"fmt"
"log"
"time"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -55,16 +56,33 @@ var AgentCmd = &cobra.Command{
Short: "Run the Unkey agent",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {

logger := logging.New(runtimeConfig.verbose).With().Str("version", version.Version).Logger()

logger.Info().Any("runtimeConfig", runtimeConfig).Msg("Starting Unkey Agent")

e := env.Env{
ErrorHandler: func(err error) {
logger.Fatal().Err(err).Msg("unable to load environment variable")
log.Fatalf("unable to load environment variable: %s", err.Error())
},
}
logConfig := &logging.Config{
Debug: runtimeConfig.verbose,
}
if runtimeConfig.enableAxiom {
axiomWriter, err := logging.NewAxiomWriter(logging.AxiomWriterConfig{
AxiomToken: e.String("AXIOM_TOKEN"),
AxiomOrgId: e.String("AXIOM_ORG_ID"),
})
if err != nil {
log.Fatalf("unable to create axiom writer: %s", err.Error())
}
logConfig.Writer = append(logConfig.Writer, axiomWriter)
}

logger, err := logging.New(logConfig)
if err != nil {
log.Fatalf("unable to create logger: %s", err.Error())
}
logger = logger.With().Str("version", version.Version).Logger()

logger.Info().Any("runtimeConfig", runtimeConfig).Msg("Starting Unkey Agent")

region := e.String("FLY_REGION", "local")
allocId := e.String("FLY_ALLOC_ID", "")
logger = logger.With().Str("region", region).Logger()
Expand Down
12 changes: 12 additions & 0 deletions apps/agent/pkg/analytics/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,21 @@ type KeyVerificationV1Event struct {
Time int64 `json:"time"`
}

type DeniedReason string

const (
DeniedRateLimited DeniedReason = "RATE_LIMITED"
DeniedUsageExceeded DeniedReason = "USAGE_EXCEEDED"
)

type KeyVerificationEvent struct {
ApiId string `json:"apiId"`
WorkspaceId string `json:"workspaceId"`
KeyId string `json:"keyId"`

// Deprecated, use `Denied` instead
Ratelimited bool `json:"ratelimited"`
// Deprecated, use `Denied` instead
UsageExceeded bool `json:"usageExceeded"`
Time int64 `json:"time"`

Expand All @@ -28,6 +38,8 @@ type KeyVerificationEvent struct {
IpAddress string `json:"ipAddress"`
UserAgent string `json:"userAgent"`

Denied DeniedReason `json:"deniedReason"`

// custom field the user may provide, like a URL or some id
RequestedResource string `json:"requestedResource"`
}
Expand Down
8 changes: 1 addition & 7 deletions apps/agent/pkg/analytics/tinybird/tinybird.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,7 @@ func (t *Tinybird) consume() {
return
case e := <-t.keyVerificationsC:

err := t.publishEvent("key_verifications__v1", analytics.KeyVerificationV1Event{
WorkspaceId: e.WorkspaceId,
ApiId: e.ApiId,
KeyId: e.KeyId,
Ratelimited: e.Ratelimited,
Time: e.Time,
})
err := t.publishEvent("key_verifications__v1", e)
if err != nil {
t.logger.Err(err).Msg("unable to publish v1 event to tinybird")
}
Expand Down
60 changes: 60 additions & 0 deletions apps/agent/pkg/logging/axiom.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package logging

import (
"context"
"encoding/json"
"fmt"

"log"
"time"

ax "github.com/axiomhq/axiom-go/axiom"
)

type AxiomWriter struct {
eventsC chan ax.Event
}

type AxiomWriterConfig struct {
AxiomToken string
AxiomOrgId string
}

func NewAxiomWriter(config AxiomWriterConfig) (*AxiomWriter, error) {

client, err := ax.NewClient(
ax.SetPersonalTokenConfig(config.AxiomToken, config.AxiomOrgId),
)
if err != nil {
return nil, fmt.Errorf("unable to create axiom client")
}
a := &AxiomWriter{
eventsC: make(chan ax.Event),
}

go func() {
_, err := client.IngestChannel(context.Background(), "agent", a.eventsC)
if err != nil {
log.Print("unable to ingest to axiom")
}
}()

return a, nil
}

func (aw *AxiomWriter) Close() {
close(aw.eventsC)
}

func (aw *AxiomWriter) Write(p []byte) (int, error) {
e := make(map[string]any)

err := json.Unmarshal(p, &e)
if err != nil {
return 0, err
}
e["_time"] = time.Now().UnixMilli()

aw.eventsC <- e
return len(p), nil
}
34 changes: 26 additions & 8 deletions apps/agent/pkg/logging/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package logging

import (
"fmt"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"io"
"os"
"path/filepath"
"runtime"
"strconv"
"strings"

"github.com/rs/zerolog"
)

type Logger = zerolog.Logger
Expand All @@ -30,16 +31,33 @@ func init() {

zerolog.TimeFieldFormat = timeFormat

log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: timeFormat})
}

func New(debug ...bool) Logger {
isDebug := len(debug) > 0 && debug[0]
if isDebug {
return log.Level(zerolog.DebugLevel).With().Timestamp().Caller().Logger()
type Config struct {
Debug bool
Writer []io.Writer
}

func New(config *Config) (Logger, error) {
if config == nil {
config = &Config{}
}
consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: timeFormat}

writers := []io.Writer{consoleWriter}
if len(config.Writer) > 0 {
writers = append(writers, config.Writer...)
}

multi := zerolog.MultiLevelWriter(writers...)

logger := zerolog.New(multi).With().Timestamp().Caller().Logger()
if config.Debug {
logger.Level(zerolog.DebugLevel)
} else {
return log.Level(zerolog.InfoLevel).With().Timestamp().Caller().Logger()
logger.Level(zerolog.InfoLevel)
}
return logger, nil
}

func NewNoopLogger() Logger {
Expand Down
3 changes: 2 additions & 1 deletion apps/agent/pkg/metrics/axiom.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

ax "github.com/axiomhq/axiom-go/axiom"
"github.com/unkeyed/unkey/apps/agent/pkg/logging"
"github.com/unkeyed/unkey/apps/agent/pkg/util"
)

type axiom struct {
Expand Down Expand Up @@ -49,7 +50,7 @@ func (m *axiom) Close() {
}

func (m *axiom) report(metric metricId, r any) {
e := toMap(r)
e := util.StructToMap(r)
e["metric"] = metric
e["_time"] = time.Now().UnixMilli()
e["region"] = m.region
Expand Down
35 changes: 22 additions & 13 deletions apps/agent/pkg/server/key_verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,19 +145,28 @@ func (s *Server) verifyKey(c *fiber.Ctx) error {
// Send usage to tinybird
// ---------------------------------------------------------------------------------------------

defer s.analytics.PublishKeyVerificationEvent(ctx, analytics.KeyVerificationEvent{
WorkspaceId: key.WorkspaceId,
ApiId: api.Id,
KeyId: key.Id,
Ratelimited: res.Code == errors.RATELIMITED,
UsageExceeded: res.Code == errors.KEY_USAGE_EXCEEDED,
Time: time.Now().UnixMilli(),
Region: s.region,
EdgeRegion: c.Get("Fly-Region"),
UserAgent: c.Get("User-Agent"),
IpAddress: c.Get("Fly-Client-IP"),
RequestedResource: req.X.Resource,
})
defer func() {
var denied analytics.DeniedReason
switch res.Code {
case errors.KEY_USAGE_EXCEEDED:
denied = analytics.DeniedUsageExceeded
case errors.RATELIMITED:
denied = analytics.DeniedRateLimited
}

s.analytics.PublishKeyVerificationEvent(ctx, analytics.KeyVerificationEvent{
WorkspaceId: key.WorkspaceId,
ApiId: api.Id,
KeyId: key.Id,
Denied: denied,
Time: time.Now().UnixMilli(),
Region: s.region,
EdgeRegion: c.Get("Fly-Region"),
UserAgent: c.Get("User-Agent"),
IpAddress: c.Get("Fly-Client-IP"),
RequestedResource: req.X.Resource,
})
}()

if !key.Expires.IsZero() {
res.Expires = key.Expires.UnixMilli()
Expand Down
2 changes: 1 addition & 1 deletion apps/agent/pkg/server/key_verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ func TestVerifyKey_WithRemaining(t *testing.T) {
require.NoError(t, err)

srv := New(Config{
Logger: logging.New(),
Logger: logging.NewNoopLogger(),
KeyCache: cache.NewNoopCache[entities.Key](),
ApiCache: cache.NewNoopCache[entities.Api](),
Database: resources.Database,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package metrics
package util

import (
"reflect"
)

func toMap(s any) map[string]any {
// StructToMap converts a struct to a map using its json tags
func StructToMap(s any) map[string]any {
obj := map[string]any{}
if s == nil {
return obj
Expand All @@ -21,7 +22,7 @@ func toMap(s any) map[string]any {
field := reflectValue.Field(i).Interface()
if tag != "" && tag != "-" {
if v.Field(i).Type.Kind() == reflect.Struct {
obj[tag] = toMap(field)
obj[tag] = StructToMap(field)
} else {
obj[tag] = field
}
Expand Down
57 changes: 57 additions & 0 deletions apps/agent/pkg/util/convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package util_test

import (
"testing"

"github.com/stretchr/testify/require"
"github.com/unkeyed/unkey/apps/agent/pkg/util"
)

type TestStruct1 struct {
Field1 string `json:"field1"`
Field2 int `json:"field2"`
}

type TestStruct2 struct {
Field3 string `json:"field3"`
Field4 int `json:"field4"`
}

type NestedStruct struct {
Inner TestStruct2 `json:"inner"`
}

func TestStructToMap_NilInput(t *testing.T) {
result := util.StructToMap(nil)
require.Empty(t, result)
}

func TestStructToMap_SimpleStruct(t *testing.T) {
input := TestStruct1{
Field1: "value1",
Field2: 42,
}
expected := map[string]interface{}{
"field1": "value1",
"field2": 42,
}
result := util.StructToMap(input)
require.Equal(t, expected, result)
}

func TestStructToMap_NestedStruct(t *testing.T) {
input := NestedStruct{
Inner: TestStruct2{
Field3: "value3",
Field4: 99,
},
}
expected := map[string]interface{}{
"inner": map[string]interface{}{
"field3": "value3",
"field4": 99,
},
}
result := util.StructToMap(input)
require.Equal(t, expected, result)
}
5 changes: 4 additions & 1 deletion apps/agent/tools/bootstrap/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ import (

func main() {
ctx := context.Background()
logger := logging.New()
logger, err := logging.New(nil)
if err != nil {
panic(err)
}
e := env.Env{ErrorHandler: func(err error) { logger.Err(err).Msg("unable to load env") }}

seedDb, err := sql.Open("mysql", e.String("DATABASE_DSN"))
Expand Down
19 changes: 0 additions & 19 deletions apps/logshipper/fly.toml

This file was deleted.

Loading

1 comment on commit 1893a0a

@vercel
Copy link

@vercel vercel bot commented on 1893a0a Sep 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

unkey – ./

unkey-unkey.vercel.app
unkey.vercel.app
www.unkey.dev
unkey.dev
unkey-git-main-unkey.vercel.app

Please sign in to comment.