Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
dcd5f9a
reworking anomalies
dawkaka Jan 6, 2026
491ea30
tables setup
dawkaka Jan 7, 2026
9a9a995
towards errors and log patterns anomaly detection
dawkaka Jan 7, 2026
545a9ad
processErrors
dawkaka Jan 7, 2026
1a1bf26
make compile
dawkaka Jan 7, 2026
cb527f8
Auto-format code with fourmolu
github-actions[bot] Jan 7, 2026
72f7652
new error and log pattern monitoring triggers
dawkaka Jan 8, 2026
e6bcb2a
get existing pattern from log patterns table
dawkaka Jan 8, 2026
c5ede37
Auto-format code with fourmolu
github-actions[bot] Jan 8, 2026
faaeeb1
Update cabal.project to include proto-lens-setup version constraint
dawkaka Jan 8, 2026
68158cf
build foundation for error and log pattern issues
dawkaka Jan 8, 2026
fc717f2
different issue types creation and bj handlers
dawkaka Jan 8, 2026
924af72
Auto-format code with fourmolu
github-actions[bot] Jan 8, 2026
338f2bd
fix LogPatterns typo
dawkaka Jan 8, 2026
117aa81
Auto-format code with fourmolu
github-actions[bot] Jan 8, 2026
11a10d7
towards api change issues presentation
dawkaka Jan 8, 2026
1f717b5
improve runtim error handdlinging and notification formatting
dawkaka Jan 8, 2026
dd77424
Auto-format code with fourmolu
github-actions[bot] Jan 8, 2026
d48fc28
error_event triger after error update/insertion
dawkaka Jan 8, 2026
91852de
complete switch to Errors.ATError
dawkaka Jan 8, 2026
9a70a27
Auto-format code with fourmolu
github-actions[bot] Jan 8, 2026
16f3f8c
towards api change anomalies
dawkaka Jan 9, 2026
d3623a2
Auto-format code with fourmolu
github-actions[bot] Jan 9, 2026
b395ac0
feat: new endpoint anomaly alerts
dawkaka Jan 9, 2026
2d55973
Auto-format code with fourmolu
github-actions[bot] Jan 9, 2026
ec4aeaf
new endpoint issue alert,
dawkaka Jan 9, 2026
2b66135
Auto-format code with fourmolu
github-actions[bot] Jan 9, 2026
80615ba
new baseline state type
dawkaka Jan 9, 2026
478bc66
new shape issue handling, aggreage shapes
dawkaka Jan 9, 2026
c8ea501
Auto-format code with fourmolu
github-actions[bot] Jan 9, 2026
3e32454
fix migration files and remove baselines table
dawkaka Jan 9, 2026
4c46af4
more migration files cleanups
dawkaka Jan 9, 2026
3d00ee2
fix drop trigger syntax
dawkaka Jan 9, 2026
d31b251
initialize added fields to ATError
dawkaka Jan 10, 2026
0e91243
improved sentry level error fingerprinting
dawkaka Jan 10, 2026
34994cd
group stack trace using language instead of sdk types
dawkaka Jan 10, 2026
a37aa8b
fix cyclic dependency and use new fingerprint function
dawkaka Jan 10, 2026
39f9003
Auto-format code with fourmolu
github-actions[bot] Jan 10, 2026
9042cf5
remove proto lens contraint
dawkaka Jan 10, 2026
c1e0db4
bug fixes and cleanups
dawkaka Jan 10, 2026
be5e7a1
Auto-format code with fourmolu
github-actions[bot] Jan 10, 2026
8cfb141
add .claude to .gitignore
dawkaka Jan 10, 2026
02e07c1
add extra shape and endpoint fields
dawkaka Jan 11, 2026
3f11576
manual enhancement title, desc, and criticality gen
dawkaka Jan 14, 2026
dc38fc4
handle all anomaly types in ui
dawkaka Jan 14, 2026
c102eeb
fix issues seelct
dawkaka Jan 14, 2026
ac3ac10
update proto-lense fork
dawkaka Jan 14, 2026
6d7bb6f
Auto-format code with fourmolu
github-actions[bot] Jan 14, 2026
6bdba43
error insert bug fixes
dawkaka Jan 14, 2026
0042692
fix anomaly trace view
dawkaka Jan 14, 2026
a9c8d79
log pattern extraction improvements
dawkaka Jan 14, 2026
51e55b7
log pattern bug fixes
dawkaka Jan 15, 2026
75d27ab
Auto-format code with fourmolu
github-actions[bot] Jan 15, 2026
574756b
fix new new shape and new field processing
dawkaka Jan 15, 2026
9e66d4c
fix shape issue insertion
dawkaka Jan 16, 2026
23ba849
fsex
dawkaka Jan 16, 2026
7d3da4a
improve anomaly display ui
dawkaka Jan 16, 2026
0fbca39
Auto-format code with fourmolu
github-actions[bot] Jan 16, 2026
08ff061
Merge origin/master (squashed)
dawkaka Jan 16, 2026
cbb6a64
fix endpoint error, volumne and latency detections
dawkaka Jan 16, 2026
3701283
Auto-format code with fourmolu
github-actions[bot] Jan 16, 2026
206e0db
issue list ui improvemnts
dawkaka Jan 17, 2026
353862e
baseline state update after 14 days
dawkaka Jan 18, 2026
e164fc1
fix build
dawkaka Jan 18, 2026
c75c61d
Auto-format code with fourmolu
github-actions[bot] Jan 18, 2026
56c4ef8
add doct test fix build errors
dawkaka Jan 18, 2026
23849d1
fix build
dawkaka Jan 18, 2026
bcd95d6
Auto-format code with fourmolu
github-actions[bot] Jan 18, 2026
6f31138
fix duration formatting
dawkaka Jan 19, 2026
5402f82
towards fixing tests
dawkaka Jan 19, 2026
2398e58
more test fixes
dawkaka Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/haskell.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '18'
node-version: '22'

- name: Generate required static files
run: |
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/pullrequest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: Setup node env
uses: actions/setup-node@v6
with:
node-version: 18
node-version: 22

- name: Generate required static files
run: |
Expand Down Expand Up @@ -125,7 +125,7 @@ jobs:
- name: Setup node env
uses: actions/setup-node@v6
with:
node-version: 18
node-version: 22

- name: Generate required static files
run: |
Expand Down Expand Up @@ -198,7 +198,7 @@ jobs:
- name: Setup node env
uses: actions/setup-node@v6
with:
node-version: 20
node-version: 22
- name: Run UI tests
run: |
cd web-components
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ cabal.project.local
stack-work
tests.log
tests_optimized.log
.claude
.playwright-mcp/
1 change: 1 addition & 0 deletions .hlint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
# - {name: System.Directory, as: Dir, importStyle: qualified, qualifiedStyle: post, asRequired: true}

- arguments:
- "-XMultilineStrings"
- "-XConstraintKinds"
- "-XDeriveGeneric"
- "-XGeneralizedNewtypeDeriving"
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Stage 1: Build frontend assets
FROM node:18-alpine AS frontend-builder
FROM node:22-alpine AS frontend-builder

WORKDIR /build

Expand Down
4 changes: 2 additions & 2 deletions cabal.project
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ source-repository-package

source-repository-package
type: git
location: https://github.com/tonyalaribe/proto-lens
tag: da3a3c7d8f43b7b22a3325a6706eb2aad98f41be
location: https://github.com/dawkaka/proto-lens
tag: cf4e060ea3376b68b65a9e207115f55b38cf1a12
subdir: proto-lens-setup
subdir: proto-lens-protoc
subdir: proto-lens-runtime
Expand Down
2 changes: 2 additions & 0 deletions monoscope.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ library
Models.Apis.Monitors
Models.Apis.Reports
Models.Apis.RequestDumps
Models.Apis.Errors
Models.Apis.LogPatterns
Models.Apis.Shapes
Models.Apis.Slack
Models.Projects.Dashboards
Expand Down
1 change: 0 additions & 1 deletion package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ custom-setup:
dependencies:
- base
- Cabal
- proto-lens-setup

build-tools:
- proto-lens-protoc:proto-lens-protoc
Expand Down
743 changes: 532 additions & 211 deletions src/BackgroundJobs.hs

Large diffs are not rendered by default.

37 changes: 0 additions & 37 deletions src/Models/Apis/Anomalies.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module Models.Apis.Anomalies (
AnomalyVM (..),
AnomalyActions (..),
Issue (..),
IssueL (..),
IssueEventAgg (..),
AnomalyTypes (..),
Expand Down Expand Up @@ -350,42 +349,6 @@ instance Default IssuesData where
def = IDEmpty


data Issue = Issue
{ id :: AnomalyId
, createdAt :: ZonedTime
, updatedAt :: ZonedTime
, projectId :: Projects.ProjectId
, acknowlegedAt :: Maybe ZonedTime
, anomalyType :: AnomalyTypes
, targetHash :: Text
, issueData :: IssuesData
, endpointId :: Maybe Endpoints.EndpointId
, acknowlegedBy :: Maybe Users.UserId
, archivedAt :: Maybe ZonedTime
, -- Enhanced UI fields
title :: Text
, service :: Text
, critical :: Bool
, breakingChanges :: Int
, incrementalChanges :: Int
, affectedRequests :: Int
, affectedClients :: Int
, estimatedRequests :: Text
, migrationComplexity :: Text
, recommendedAction :: Text
, requestPayloads :: Aeson [PayloadChange]
, responsePayloads :: Aeson [PayloadChange]
, -- New fields for anomaly grouping
anomalyHashes :: V.Vector Text
, endpointHash :: Text
}
deriving stock (Generic, Show)
deriving anyclass (Default, FromRow, NFData, ToRow)
deriving
(Entity)
via (GenericEntity '[Schema "apis", TableName "issues", PrimaryKey "id", FieldModifiers '[CamelToSnake]] Issue)


data IssueEventAgg = IssueEventAgg
{ count :: Int
, lastSeen :: UTCTime
Expand Down
216 changes: 213 additions & 3 deletions src/Models/Apis/Endpoints.hs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,17 @@ module Models.Apis.Endpoints (
EndpointRequestStats (..),
Host (..),
HostEvents (..),
EndpointWithCurrentRates (..),
EndpointStats (..),
bulkInsertEndpoints,
dependenciesAndEventsCount,
endpointRequestStatsByProject,
countEndpointInbox,
getEndpointsWithCurrentRates,
getEndpointStats,
updateEndpointBaseline,
getEndpointByHash,
getActiveEndpoints,
)
where

Expand All @@ -33,7 +40,7 @@ import Effectful.PostgreSQL (withConnection)
import Effectful.PostgreSQL qualified as PG
import Models.Projects.Projects qualified as Projects
import NeatInterpolation (text)
import Pkg.DeriveUtils (UUIDId (..))
import Pkg.DeriveUtils (BaselineState (..), UUIDId (..))
import Relude
import System.Types (DB)

Expand All @@ -59,6 +66,20 @@ data Endpoint = Endpoint
, hash :: Text
, outgoing :: Bool
, description :: Text
, firstTraceId :: Maybe Text
, recentTraceId :: Maybe Text
, service :: Maybe Text
, baselineState :: BaselineState
, baselineSamples :: Int
, baselineUpdatedAt :: Maybe UTCTime
, baselineErrorRateMean :: Maybe Double
, baselineErrorRateStddev :: Maybe Double
, baselineLatencyMean :: Maybe Double
, baselineLatencyStddev :: Maybe Double
, baselineLatencyP95 :: Maybe Double
, baselineLatencyP99 :: Maybe Double
, baselineVolumeHourlyMean :: Maybe Double
, baselineVolumeHourlyStddev :: Maybe Double
}
deriving stock (Eq, Generic, Show)
deriving anyclass (Default, FromRow, NFData, ToRow)
Expand All @@ -70,8 +91,8 @@ bulkInsertEndpoints :: DB es => V.Vector Endpoint -> Eff es ()
bulkInsertEndpoints endpoints = void $ PG.executeMany q $ V.toList rowsToInsert
where
q =
[sql| INSERT INTO apis.endpoints (project_id, url_path, url_params, method, host, hash, outgoing)
VALUES (?, ?, ?, ?, ?, ?, ?) ON CONFLICT (hash) DO NOTHING;
[sql| INSERT INTO apis.endpoints (project_id, url_path, url_params, method, host, hash, outgoing, first_trace_id, recent_trace_id, service)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT (hash) DO NOTHING;
|]
rowsToInsert =
endpoints <&> \endpoint ->
Expand All @@ -82,6 +103,9 @@ bulkInsertEndpoints endpoints = void $ PG.executeMany q $ V.toList rowsToInsert
, endpoint.host
, endpoint.hash
, endpoint.outgoing
, endpoint.firstTraceId
, endpoint.recentTraceId
, endpoint.service
)


Expand Down Expand Up @@ -233,3 +257,189 @@ countEndpointInbox pid host requestType = do
AND ann.acknowledged_at IS NULL
AND host = ?
|]


-- | Endpoint with current hourly rates for anomaly detection
data EndpointWithCurrentRates = EndpointWithCurrentRates
{ endpointId :: EndpointId
, endpointHash :: Text
, method :: Text
, urlPath :: Text
, host :: Text
, baselineState :: BaselineState
, baselineErrorRateMean :: Maybe Double
, baselineErrorRateStddev :: Maybe Double
, baselineLatencyMean :: Maybe Double
, baselineLatencyStddev :: Maybe Double
, baselineLatencyP95 :: Maybe Double
, baselineLatencyP99 :: Maybe Double
, baselineVolumeHourlyMean :: Maybe Double
, baselineVolumeHourlyStddev :: Maybe Double
, currentHourRequests :: Int
, currentHourErrors :: Int
, currentHourLatencyP50 :: Maybe Double
, currentHourLatencyP95 :: Maybe Double
, currentHourLatencyP99 :: Maybe Double
}
deriving stock (Generic, Show)
deriving anyclass (Default, FromRow, NFData)


-- | Endpoint stats for baseline calculation
data EndpointStats = EndpointStats
{ totalHours :: Int
, hourlyMeanRequests :: Double
, hourlyStddevRequests :: Double
, hourlyMeanErrors :: Double
, hourlyStddevErrors :: Double
, meanLatency :: Double
, stddevLatency :: Double
, p95Latency :: Double
, p99Latency :: Double
}
deriving stock (Generic, Show)
deriving anyclass (Default, FromRow, NFData)


-- | Get endpoints with their current hourly rates for anomaly detection
getEndpointsWithCurrentRates :: DB es => Projects.ProjectId -> Eff es [EndpointWithCurrentRates]
getEndpointsWithCurrentRates pid = PG.query q (pid, pid)
where
q =
[sql|
WITH current_hour_stats AS (
SELECT
attributes->'http'->>'route' AS url_path,
attributes___http___request___method AS method,
COUNT(*) AS request_count,
COUNT(*) FILTER (WHERE attributes___http___response___status_code::int >= 500 OR attributes___http___response___status_code::int = 0) AS error_count,
PERCENTILE_CONT(0.50) WITHIN GROUP (ORDER BY duration) AS p50_latency,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration) AS p95_latency,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY duration) AS p99_latency
FROM otel_logs_and_spans
WHERE project_id = ?
AND name = 'monoscope.http'
AND timestamp >= NOW() - INTERVAL '1 hour'
GROUP BY url_path, method
)
SELECT
e.id AS endpoint_id,
e.hash AS endpoint_hash,
e.method,
e.url_path,
e.host,
e.baseline_state,
e.baseline_error_rate_mean,
e.baseline_error_rate_stddev,
e.baseline_latency_mean,
e.baseline_latency_stddev,
e.baseline_latency_p95,
e.baseline_latency_p99,
e.baseline_volume_hourly_mean,
e.baseline_volume_hourly_stddev,
COALESCE(chs.request_count, 0)::int AS current_hour_requests,
COALESCE(chs.error_count, 0)::int AS current_hour_errors,
chs.p50_latency AS current_hour_latency_p50,
chs.p95_latency AS current_hour_latency_p95,
chs.p99_latency AS current_hour_latency_p99
FROM apis.endpoints e
LEFT JOIN current_hour_stats chs
ON e.url_path = chs.url_path AND e.method = chs.method
WHERE e.project_id = ?
AND e.baseline_state = 'established'
|]


-- | Get endpoint stats for baseline calculation over N hours
getEndpointStats :: DB es => Projects.ProjectId -> Text -> Int -> Eff es (Maybe EndpointStats)
getEndpointStats pid endpointHash hours = listToMaybe <$> PG.query q (pid, endpointHash, hours)
where
q =
[sql|
WITH hourly_stats AS (
SELECT
DATE_TRUNC('hour', timestamp) AS hour,
COUNT(*) AS request_count,
COUNT(*) FILTER (WHERE attributes___http___response___status_code::int >= 500 OR attributes___http___response___status_code::int = 0) AS error_count,
AVG(duration) AS avg_latency,
PERCENTILE_CONT(0.95) WITHIN GROUP (ORDER BY duration) AS p95_latency,
PERCENTILE_CONT(0.99) WITHIN GROUP (ORDER BY duration) AS p99_latency
FROM otel_logs_and_spans ols
JOIN apis.endpoints e ON e.url_path = (ols.attributes->'http'->>'route')
AND e.method = ols.attributes___http___request___method
WHERE ols.project_id = ?
AND e.hash = ?
AND ols.name = 'monoscope.http'
AND ols.timestamp >= NOW() - MAKE_INTERVAL(hours => ?)
GROUP BY DATE_TRUNC('hour', timestamp)
)
SELECT
COUNT(*)::int AS total_hours,
COALESCE(AVG(request_count), 0)::float8 AS hourly_mean_requests,
COALESCE(STDDEV(request_count), 0)::float8 AS hourly_stddev_requests,
COALESCE(AVG(error_count::float / NULLIF(request_count, 0)), 0)::float8 AS hourly_mean_errors,
COALESCE(STDDEV(error_count::float / NULLIF(request_count, 0)), 0)::float8 AS hourly_stddev_errors,
COALESCE(AVG(avg_latency), 0)::float8 AS mean_latency,
COALESCE(STDDEV(avg_latency), 0)::float8 AS stddev_latency,
COALESCE(AVG(p95_latency), 0)::float8 AS p95_latency,
COALESCE(AVG(p99_latency), 0)::float8 AS p99_latency
FROM hourly_stats
HAVING COUNT(*) > 0
|]


-- | Update endpoint baseline values
updateEndpointBaseline :: DB es => EndpointId -> BaselineState -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Double -> Int -> Eff es ()
updateEndpointBaseline eid bState errMean errStddev latMean latStddev latP95 latP99 volMean volStddev samples =
void $ PG.execute q (bState, errMean, errStddev, latMean, latStddev, latP95, latP99, volMean, volStddev, samples, eid)
where
q =
[sql|
UPDATE apis.endpoints
SET baseline_state = ?,
baseline_error_rate_mean = ?,
baseline_error_rate_stddev = ?,
baseline_latency_mean = ?,
baseline_latency_stddev = ?,
baseline_latency_p95 = ?,
baseline_latency_p99 = ?,
baseline_volume_hourly_mean = ?,
baseline_volume_hourly_stddev = ?,
baseline_samples = ?,
baseline_updated_at = NOW()
WHERE id = ?
|]


-- | Get endpoint by hash
getEndpointByHash :: DB es => Projects.ProjectId -> Text -> Eff es (Maybe Endpoint)
getEndpointByHash pid hash = listToMaybe <$> PG.query q (pid, hash)
where
q =
[sql|
SELECT id, created_at, updated_at, project_id, url_path, url_params, method, host, hash, outgoing, description,
first_trace_id, recent_trace_id, service,
baseline_state, baseline_samples, baseline_updated_at,
baseline_error_rate_mean, baseline_error_rate_stddev,
baseline_latency_mean, baseline_latency_stddev, baseline_latency_p95, baseline_latency_p99,
baseline_volume_hourly_mean, baseline_volume_hourly_stddev
FROM apis.endpoints
WHERE project_id = ? AND hash = ?
|]


-- | Get all active endpoints for a project (for baseline calculation)
getActiveEndpoints :: DB es => Projects.ProjectId -> Eff es [Endpoint]
getActiveEndpoints pid = PG.query q (Only pid)
where
q =
[sql|
SELECT id, created_at, updated_at, project_id, url_path, url_params, method, host, hash, outgoing, description,
first_trace_id, recent_trace_id, service,
baseline_state, baseline_samples, baseline_updated_at,
baseline_error_rate_mean, baseline_error_rate_stddev,
baseline_latency_mean, baseline_latency_stddev, baseline_latency_p95, baseline_latency_p99,
baseline_volume_hourly_mean, baseline_volume_hourly_stddev
FROM apis.endpoints
WHERE project_id = ?
|]
Loading