@wtfalch/reporting 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -6
- package/dist/analytics/write.js +3 -0
- package/dist/deploy.d.ts +11 -0
- package/dist/deploy.js +13 -0
- package/dist/errors/capture.js +4 -0
- package/dist/errors/reader.js +1 -0
- package/dist/index.js +16 -1
- package/dist/migrations/0009_release_color.sql +33 -0
- package/dist/migrations/0010_requests.sql +27 -0
- package/dist/next/count.d.ts +49 -0
- package/dist/next/count.js +100 -0
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +2 -0
- package/dist/next/release-health.d.ts +24 -0
- package/dist/next/release-health.js +122 -0
- package/dist/requests.d.ts +29 -0
- package/dist/requests.js +82 -0
- package/dist/tables.d.ts +391 -1
- package/dist/tables.js +20 -0
- package/dist/types.d.ts +7 -0
- package/dist/writer.js +2 -0
- package/package.json +1 -5
- package/dist/fake/index.d.ts +0 -22
- package/dist/fake/index.js +0 -157
package/dist/requests.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { sql } from 'drizzle-orm';
|
|
2
|
+
import { deployTags } from './deploy.js';
|
|
3
|
+
import { reportingRequests } from './tables.js';
|
|
4
|
+
/**
|
|
5
|
+
* Counts responses in memory and writes one upsert per minute bucket, so a
|
|
6
|
+
* request costs a map increment and the database sees a handful of rows a
|
|
7
|
+
* minute. `errors` is status >= 500. A failed write puts its counts back for
|
|
8
|
+
* the next one; the process dying loses at most `flushEveryMs` of counts.
|
|
9
|
+
*/
|
|
10
|
+
export class RequestCounter {
|
|
11
|
+
o;
|
|
12
|
+
buckets = new Map();
|
|
13
|
+
timer = null;
|
|
14
|
+
constructor(o) {
|
|
15
|
+
this.o = o;
|
|
16
|
+
}
|
|
17
|
+
/** Counts one response. */
|
|
18
|
+
record(status) {
|
|
19
|
+
this.add(status >= 500 ? 1 : 0, 1);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Counts one 5xx for a request already counted by `record`, such as a page
|
|
23
|
+
* that threw after the proxy let it through. Adds no request.
|
|
24
|
+
*/
|
|
25
|
+
recordError() {
|
|
26
|
+
this.add(1, 0);
|
|
27
|
+
}
|
|
28
|
+
add(errors, requests) {
|
|
29
|
+
const minute = Math.floor(this.o.now().getTime() / 60_000) * 60_000;
|
|
30
|
+
const b = this.buckets.get(minute) ?? { requests: 0, errors: 0 };
|
|
31
|
+
b.requests += requests;
|
|
32
|
+
b.errors += errors;
|
|
33
|
+
this.buckets.set(minute, b);
|
|
34
|
+
if (!this.timer) {
|
|
35
|
+
this.timer = setTimeout(() => {
|
|
36
|
+
this.timer = null;
|
|
37
|
+
void this.flush();
|
|
38
|
+
}, this.o.flushEveryMs);
|
|
39
|
+
this.timer.unref?.();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async flush() {
|
|
43
|
+
if (this.buckets.size === 0)
|
|
44
|
+
return;
|
|
45
|
+
const taken = [...this.buckets];
|
|
46
|
+
this.buckets.clear();
|
|
47
|
+
const { color, release } = deployTags();
|
|
48
|
+
try {
|
|
49
|
+
await this.o.db
|
|
50
|
+
.insert(reportingRequests)
|
|
51
|
+
.values(taken.map(([minute, c]) => ({
|
|
52
|
+
bucket: new Date(minute),
|
|
53
|
+
site: this.o.site,
|
|
54
|
+
color: color ?? '',
|
|
55
|
+
release: release ?? '',
|
|
56
|
+
requests: c.requests,
|
|
57
|
+
errors: c.errors,
|
|
58
|
+
})))
|
|
59
|
+
.onConflictDoUpdate({
|
|
60
|
+
target: [
|
|
61
|
+
reportingRequests.bucket,
|
|
62
|
+
reportingRequests.site,
|
|
63
|
+
reportingRequests.color,
|
|
64
|
+
reportingRequests.release,
|
|
65
|
+
],
|
|
66
|
+
set: {
|
|
67
|
+
requests: sql `${reportingRequests.requests} + excluded.requests`,
|
|
68
|
+
errors: sql `${reportingRequests.errors} + excluded.errors`,
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
for (const [minute, c] of taken) {
|
|
74
|
+
const b = this.buckets.get(minute) ?? { requests: 0, errors: 0 };
|
|
75
|
+
b.requests += c.requests;
|
|
76
|
+
b.errors += c.errors;
|
|
77
|
+
this.buckets.set(minute, b);
|
|
78
|
+
}
|
|
79
|
+
this.o.log.error({ err: error instanceof Error ? error.name : 'unknown' }, 'reporting: request counter flush failed');
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
package/dist/tables.d.ts
CHANGED
|
@@ -130,6 +130,40 @@ export declare const reportingEvents: import("drizzle-orm/pg-core").PgTableWithC
|
|
|
130
130
|
identity: undefined;
|
|
131
131
|
generated: undefined;
|
|
132
132
|
}, {}, {}>;
|
|
133
|
+
release: import("drizzle-orm/pg-core").PgColumn<{
|
|
134
|
+
name: "release";
|
|
135
|
+
tableName: "reporting_events";
|
|
136
|
+
dataType: "string";
|
|
137
|
+
columnType: "PgText";
|
|
138
|
+
data: string;
|
|
139
|
+
driverParam: string;
|
|
140
|
+
notNull: false;
|
|
141
|
+
hasDefault: false;
|
|
142
|
+
isPrimaryKey: false;
|
|
143
|
+
isAutoincrement: false;
|
|
144
|
+
hasRuntimeDefault: false;
|
|
145
|
+
enumValues: [string, ...string[]];
|
|
146
|
+
baseColumn: never;
|
|
147
|
+
identity: undefined;
|
|
148
|
+
generated: undefined;
|
|
149
|
+
}, {}, {}>;
|
|
150
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
151
|
+
name: "color";
|
|
152
|
+
tableName: "reporting_events";
|
|
153
|
+
dataType: "string";
|
|
154
|
+
columnType: "PgText";
|
|
155
|
+
data: string;
|
|
156
|
+
driverParam: string;
|
|
157
|
+
notNull: false;
|
|
158
|
+
hasDefault: false;
|
|
159
|
+
isPrimaryKey: false;
|
|
160
|
+
isAutoincrement: false;
|
|
161
|
+
hasRuntimeDefault: false;
|
|
162
|
+
enumValues: [string, ...string[]];
|
|
163
|
+
baseColumn: never;
|
|
164
|
+
identity: undefined;
|
|
165
|
+
generated: undefined;
|
|
166
|
+
}, {}, {}>;
|
|
133
167
|
tenantId: import("drizzle-orm/pg-core").PgColumn<{
|
|
134
168
|
name: "tenant_id";
|
|
135
169
|
tableName: "reporting_events";
|
|
@@ -878,6 +912,40 @@ export declare const reportingAnalytics: import("drizzle-orm/pg-core").PgTableWi
|
|
|
878
912
|
identity: undefined;
|
|
879
913
|
generated: undefined;
|
|
880
914
|
}, {}, {}>;
|
|
915
|
+
release: import("drizzle-orm/pg-core").PgColumn<{
|
|
916
|
+
name: "release";
|
|
917
|
+
tableName: "reporting_analytics";
|
|
918
|
+
dataType: "string";
|
|
919
|
+
columnType: "PgText";
|
|
920
|
+
data: string;
|
|
921
|
+
driverParam: string;
|
|
922
|
+
notNull: false;
|
|
923
|
+
hasDefault: false;
|
|
924
|
+
isPrimaryKey: false;
|
|
925
|
+
isAutoincrement: false;
|
|
926
|
+
hasRuntimeDefault: false;
|
|
927
|
+
enumValues: [string, ...string[]];
|
|
928
|
+
baseColumn: never;
|
|
929
|
+
identity: undefined;
|
|
930
|
+
generated: undefined;
|
|
931
|
+
}, {}, {}>;
|
|
932
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
933
|
+
name: "color";
|
|
934
|
+
tableName: "reporting_analytics";
|
|
935
|
+
dataType: "string";
|
|
936
|
+
columnType: "PgText";
|
|
937
|
+
data: string;
|
|
938
|
+
driverParam: string;
|
|
939
|
+
notNull: false;
|
|
940
|
+
hasDefault: false;
|
|
941
|
+
isPrimaryKey: false;
|
|
942
|
+
isAutoincrement: false;
|
|
943
|
+
hasRuntimeDefault: false;
|
|
944
|
+
enumValues: [string, ...string[]];
|
|
945
|
+
baseColumn: never;
|
|
946
|
+
identity: undefined;
|
|
947
|
+
generated: undefined;
|
|
948
|
+
}, {}, {}>;
|
|
881
949
|
};
|
|
882
950
|
dialect: "pg";
|
|
883
951
|
}>;
|
|
@@ -1494,6 +1562,23 @@ export declare const reportingErrors: import("drizzle-orm/pg-core").PgTableWithC
|
|
|
1494
1562
|
identity: undefined;
|
|
1495
1563
|
generated: undefined;
|
|
1496
1564
|
}, {}, {}>;
|
|
1565
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
1566
|
+
name: "color";
|
|
1567
|
+
tableName: "reporting_errors";
|
|
1568
|
+
dataType: "string";
|
|
1569
|
+
columnType: "PgText";
|
|
1570
|
+
data: string;
|
|
1571
|
+
driverParam: string;
|
|
1572
|
+
notNull: false;
|
|
1573
|
+
hasDefault: false;
|
|
1574
|
+
isPrimaryKey: false;
|
|
1575
|
+
isAutoincrement: false;
|
|
1576
|
+
hasRuntimeDefault: false;
|
|
1577
|
+
enumValues: [string, ...string[]];
|
|
1578
|
+
baseColumn: never;
|
|
1579
|
+
identity: undefined;
|
|
1580
|
+
generated: undefined;
|
|
1581
|
+
}, {}, {}>;
|
|
1497
1582
|
firstSeenAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
1498
1583
|
name: "first_seen_at";
|
|
1499
1584
|
tableName: "reporting_errors";
|
|
@@ -1741,17 +1826,128 @@ export type ReportingErrorRow = typeof reportingErrors.$inferSelect;
|
|
|
1741
1826
|
* Counts, times and
|
|
1742
1827
|
* `tenantId` are the tenant's own (reporting_error_tenants).
|
|
1743
1828
|
*/
|
|
1744
|
-
export type TenantErrorRow = Omit<ReportingErrorRow, 'message' | 'stack' | 'runtime' | 'release' | 'requestId' | 'resolvedBy' | 'state' | 'resolvedAt'> & {
|
|
1829
|
+
export type TenantErrorRow = Omit<ReportingErrorRow, 'message' | 'stack' | 'runtime' | 'release' | 'color' | 'requestId' | 'resolvedBy' | 'state' | 'resolvedAt'> & {
|
|
1745
1830
|
readonly message: null;
|
|
1746
1831
|
readonly stack: null;
|
|
1747
1832
|
readonly runtime: null;
|
|
1748
1833
|
readonly release: null;
|
|
1834
|
+
readonly color: null;
|
|
1749
1835
|
readonly requestId: null;
|
|
1750
1836
|
readonly resolvedBy: null;
|
|
1751
1837
|
/** Triage state is shared across tenants, so a tenant cannot see it. */
|
|
1752
1838
|
readonly state: null;
|
|
1753
1839
|
readonly resolvedAt: null;
|
|
1754
1840
|
};
|
|
1841
|
+
/** Request and 5xx counts per minute, color and release; mirrored from migrations/0010_requests.sql. */
|
|
1842
|
+
export declare const reportingRequests: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
1843
|
+
name: "reporting_requests";
|
|
1844
|
+
schema: undefined;
|
|
1845
|
+
columns: {
|
|
1846
|
+
bucket: import("drizzle-orm/pg-core").PgColumn<{
|
|
1847
|
+
name: "bucket";
|
|
1848
|
+
tableName: "reporting_requests";
|
|
1849
|
+
dataType: "date";
|
|
1850
|
+
columnType: "PgTimestamp";
|
|
1851
|
+
data: Date;
|
|
1852
|
+
driverParam: string;
|
|
1853
|
+
notNull: true;
|
|
1854
|
+
hasDefault: false;
|
|
1855
|
+
isPrimaryKey: false;
|
|
1856
|
+
isAutoincrement: false;
|
|
1857
|
+
hasRuntimeDefault: false;
|
|
1858
|
+
enumValues: undefined;
|
|
1859
|
+
baseColumn: never;
|
|
1860
|
+
identity: undefined;
|
|
1861
|
+
generated: undefined;
|
|
1862
|
+
}, {}, {}>;
|
|
1863
|
+
site: import("drizzle-orm/pg-core").PgColumn<{
|
|
1864
|
+
name: "site";
|
|
1865
|
+
tableName: "reporting_requests";
|
|
1866
|
+
dataType: "string";
|
|
1867
|
+
columnType: "PgText";
|
|
1868
|
+
data: string;
|
|
1869
|
+
driverParam: string;
|
|
1870
|
+
notNull: true;
|
|
1871
|
+
hasDefault: false;
|
|
1872
|
+
isPrimaryKey: false;
|
|
1873
|
+
isAutoincrement: false;
|
|
1874
|
+
hasRuntimeDefault: false;
|
|
1875
|
+
enumValues: [string, ...string[]];
|
|
1876
|
+
baseColumn: never;
|
|
1877
|
+
identity: undefined;
|
|
1878
|
+
generated: undefined;
|
|
1879
|
+
}, {}, {}>;
|
|
1880
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
1881
|
+
name: "color";
|
|
1882
|
+
tableName: "reporting_requests";
|
|
1883
|
+
dataType: "string";
|
|
1884
|
+
columnType: "PgText";
|
|
1885
|
+
data: string;
|
|
1886
|
+
driverParam: string;
|
|
1887
|
+
notNull: true;
|
|
1888
|
+
hasDefault: true;
|
|
1889
|
+
isPrimaryKey: false;
|
|
1890
|
+
isAutoincrement: false;
|
|
1891
|
+
hasRuntimeDefault: false;
|
|
1892
|
+
enumValues: [string, ...string[]];
|
|
1893
|
+
baseColumn: never;
|
|
1894
|
+
identity: undefined;
|
|
1895
|
+
generated: undefined;
|
|
1896
|
+
}, {}, {}>;
|
|
1897
|
+
release: import("drizzle-orm/pg-core").PgColumn<{
|
|
1898
|
+
name: "release";
|
|
1899
|
+
tableName: "reporting_requests";
|
|
1900
|
+
dataType: "string";
|
|
1901
|
+
columnType: "PgText";
|
|
1902
|
+
data: string;
|
|
1903
|
+
driverParam: string;
|
|
1904
|
+
notNull: true;
|
|
1905
|
+
hasDefault: true;
|
|
1906
|
+
isPrimaryKey: false;
|
|
1907
|
+
isAutoincrement: false;
|
|
1908
|
+
hasRuntimeDefault: false;
|
|
1909
|
+
enumValues: [string, ...string[]];
|
|
1910
|
+
baseColumn: never;
|
|
1911
|
+
identity: undefined;
|
|
1912
|
+
generated: undefined;
|
|
1913
|
+
}, {}, {}>;
|
|
1914
|
+
requests: import("drizzle-orm/pg-core").PgColumn<{
|
|
1915
|
+
name: "requests";
|
|
1916
|
+
tableName: "reporting_requests";
|
|
1917
|
+
dataType: "number";
|
|
1918
|
+
columnType: "PgBigInt53";
|
|
1919
|
+
data: number;
|
|
1920
|
+
driverParam: string | number;
|
|
1921
|
+
notNull: true;
|
|
1922
|
+
hasDefault: true;
|
|
1923
|
+
isPrimaryKey: false;
|
|
1924
|
+
isAutoincrement: false;
|
|
1925
|
+
hasRuntimeDefault: false;
|
|
1926
|
+
enumValues: undefined;
|
|
1927
|
+
baseColumn: never;
|
|
1928
|
+
identity: undefined;
|
|
1929
|
+
generated: undefined;
|
|
1930
|
+
}, {}, {}>;
|
|
1931
|
+
errors: import("drizzle-orm/pg-core").PgColumn<{
|
|
1932
|
+
name: "errors";
|
|
1933
|
+
tableName: "reporting_requests";
|
|
1934
|
+
dataType: "number";
|
|
1935
|
+
columnType: "PgBigInt53";
|
|
1936
|
+
data: number;
|
|
1937
|
+
driverParam: string | number;
|
|
1938
|
+
notNull: true;
|
|
1939
|
+
hasDefault: true;
|
|
1940
|
+
isPrimaryKey: false;
|
|
1941
|
+
isAutoincrement: false;
|
|
1942
|
+
hasRuntimeDefault: false;
|
|
1943
|
+
enumValues: undefined;
|
|
1944
|
+
baseColumn: never;
|
|
1945
|
+
identity: undefined;
|
|
1946
|
+
generated: undefined;
|
|
1947
|
+
}, {}, {}>;
|
|
1948
|
+
};
|
|
1949
|
+
dialect: "pg";
|
|
1950
|
+
}>;
|
|
1755
1951
|
export declare const tables: {
|
|
1756
1952
|
events: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
1757
1953
|
name: "reporting_events";
|
|
@@ -1878,6 +2074,40 @@ export declare const tables: {
|
|
|
1878
2074
|
identity: undefined;
|
|
1879
2075
|
generated: undefined;
|
|
1880
2076
|
}, {}, {}>;
|
|
2077
|
+
release: import("drizzle-orm/pg-core").PgColumn<{
|
|
2078
|
+
name: "release";
|
|
2079
|
+
tableName: "reporting_events";
|
|
2080
|
+
dataType: "string";
|
|
2081
|
+
columnType: "PgText";
|
|
2082
|
+
data: string;
|
|
2083
|
+
driverParam: string;
|
|
2084
|
+
notNull: false;
|
|
2085
|
+
hasDefault: false;
|
|
2086
|
+
isPrimaryKey: false;
|
|
2087
|
+
isAutoincrement: false;
|
|
2088
|
+
hasRuntimeDefault: false;
|
|
2089
|
+
enumValues: [string, ...string[]];
|
|
2090
|
+
baseColumn: never;
|
|
2091
|
+
identity: undefined;
|
|
2092
|
+
generated: undefined;
|
|
2093
|
+
}, {}, {}>;
|
|
2094
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
2095
|
+
name: "color";
|
|
2096
|
+
tableName: "reporting_events";
|
|
2097
|
+
dataType: "string";
|
|
2098
|
+
columnType: "PgText";
|
|
2099
|
+
data: string;
|
|
2100
|
+
driverParam: string;
|
|
2101
|
+
notNull: false;
|
|
2102
|
+
hasDefault: false;
|
|
2103
|
+
isPrimaryKey: false;
|
|
2104
|
+
isAutoincrement: false;
|
|
2105
|
+
hasRuntimeDefault: false;
|
|
2106
|
+
enumValues: [string, ...string[]];
|
|
2107
|
+
baseColumn: never;
|
|
2108
|
+
identity: undefined;
|
|
2109
|
+
generated: undefined;
|
|
2110
|
+
}, {}, {}>;
|
|
1881
2111
|
tenantId: import("drizzle-orm/pg-core").PgColumn<{
|
|
1882
2112
|
name: "tenant_id";
|
|
1883
2113
|
tableName: "reporting_events";
|
|
@@ -2624,6 +2854,40 @@ export declare const tables: {
|
|
|
2624
2854
|
identity: undefined;
|
|
2625
2855
|
generated: undefined;
|
|
2626
2856
|
}, {}, {}>;
|
|
2857
|
+
release: import("drizzle-orm/pg-core").PgColumn<{
|
|
2858
|
+
name: "release";
|
|
2859
|
+
tableName: "reporting_analytics";
|
|
2860
|
+
dataType: "string";
|
|
2861
|
+
columnType: "PgText";
|
|
2862
|
+
data: string;
|
|
2863
|
+
driverParam: string;
|
|
2864
|
+
notNull: false;
|
|
2865
|
+
hasDefault: false;
|
|
2866
|
+
isPrimaryKey: false;
|
|
2867
|
+
isAutoincrement: false;
|
|
2868
|
+
hasRuntimeDefault: false;
|
|
2869
|
+
enumValues: [string, ...string[]];
|
|
2870
|
+
baseColumn: never;
|
|
2871
|
+
identity: undefined;
|
|
2872
|
+
generated: undefined;
|
|
2873
|
+
}, {}, {}>;
|
|
2874
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
2875
|
+
name: "color";
|
|
2876
|
+
tableName: "reporting_analytics";
|
|
2877
|
+
dataType: "string";
|
|
2878
|
+
columnType: "PgText";
|
|
2879
|
+
data: string;
|
|
2880
|
+
driverParam: string;
|
|
2881
|
+
notNull: false;
|
|
2882
|
+
hasDefault: false;
|
|
2883
|
+
isPrimaryKey: false;
|
|
2884
|
+
isAutoincrement: false;
|
|
2885
|
+
hasRuntimeDefault: false;
|
|
2886
|
+
enumValues: [string, ...string[]];
|
|
2887
|
+
baseColumn: never;
|
|
2888
|
+
identity: undefined;
|
|
2889
|
+
generated: undefined;
|
|
2890
|
+
}, {}, {}>;
|
|
2627
2891
|
};
|
|
2628
2892
|
dialect: "pg";
|
|
2629
2893
|
}>;
|
|
@@ -3237,6 +3501,23 @@ export declare const tables: {
|
|
|
3237
3501
|
identity: undefined;
|
|
3238
3502
|
generated: undefined;
|
|
3239
3503
|
}, {}, {}>;
|
|
3504
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
3505
|
+
name: "color";
|
|
3506
|
+
tableName: "reporting_errors";
|
|
3507
|
+
dataType: "string";
|
|
3508
|
+
columnType: "PgText";
|
|
3509
|
+
data: string;
|
|
3510
|
+
driverParam: string;
|
|
3511
|
+
notNull: false;
|
|
3512
|
+
hasDefault: false;
|
|
3513
|
+
isPrimaryKey: false;
|
|
3514
|
+
isAutoincrement: false;
|
|
3515
|
+
hasRuntimeDefault: false;
|
|
3516
|
+
enumValues: [string, ...string[]];
|
|
3517
|
+
baseColumn: never;
|
|
3518
|
+
identity: undefined;
|
|
3519
|
+
generated: undefined;
|
|
3520
|
+
}, {}, {}>;
|
|
3240
3521
|
firstSeenAt: import("drizzle-orm/pg-core").PgColumn<{
|
|
3241
3522
|
name: "first_seen_at";
|
|
3242
3523
|
tableName: "reporting_errors";
|
|
@@ -3468,4 +3749,113 @@ export declare const tables: {
|
|
|
3468
3749
|
};
|
|
3469
3750
|
dialect: "pg";
|
|
3470
3751
|
}>;
|
|
3752
|
+
requests: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
3753
|
+
name: "reporting_requests";
|
|
3754
|
+
schema: undefined;
|
|
3755
|
+
columns: {
|
|
3756
|
+
bucket: import("drizzle-orm/pg-core").PgColumn<{
|
|
3757
|
+
name: "bucket";
|
|
3758
|
+
tableName: "reporting_requests";
|
|
3759
|
+
dataType: "date";
|
|
3760
|
+
columnType: "PgTimestamp";
|
|
3761
|
+
data: Date;
|
|
3762
|
+
driverParam: string;
|
|
3763
|
+
notNull: true;
|
|
3764
|
+
hasDefault: false;
|
|
3765
|
+
isPrimaryKey: false;
|
|
3766
|
+
isAutoincrement: false;
|
|
3767
|
+
hasRuntimeDefault: false;
|
|
3768
|
+
enumValues: undefined;
|
|
3769
|
+
baseColumn: never;
|
|
3770
|
+
identity: undefined;
|
|
3771
|
+
generated: undefined;
|
|
3772
|
+
}, {}, {}>;
|
|
3773
|
+
site: import("drizzle-orm/pg-core").PgColumn<{
|
|
3774
|
+
name: "site";
|
|
3775
|
+
tableName: "reporting_requests";
|
|
3776
|
+
dataType: "string";
|
|
3777
|
+
columnType: "PgText";
|
|
3778
|
+
data: string;
|
|
3779
|
+
driverParam: string;
|
|
3780
|
+
notNull: true;
|
|
3781
|
+
hasDefault: false;
|
|
3782
|
+
isPrimaryKey: false;
|
|
3783
|
+
isAutoincrement: false;
|
|
3784
|
+
hasRuntimeDefault: false;
|
|
3785
|
+
enumValues: [string, ...string[]];
|
|
3786
|
+
baseColumn: never;
|
|
3787
|
+
identity: undefined;
|
|
3788
|
+
generated: undefined;
|
|
3789
|
+
}, {}, {}>;
|
|
3790
|
+
color: import("drizzle-orm/pg-core").PgColumn<{
|
|
3791
|
+
name: "color";
|
|
3792
|
+
tableName: "reporting_requests";
|
|
3793
|
+
dataType: "string";
|
|
3794
|
+
columnType: "PgText";
|
|
3795
|
+
data: string;
|
|
3796
|
+
driverParam: string;
|
|
3797
|
+
notNull: true;
|
|
3798
|
+
hasDefault: true;
|
|
3799
|
+
isPrimaryKey: false;
|
|
3800
|
+
isAutoincrement: false;
|
|
3801
|
+
hasRuntimeDefault: false;
|
|
3802
|
+
enumValues: [string, ...string[]];
|
|
3803
|
+
baseColumn: never;
|
|
3804
|
+
identity: undefined;
|
|
3805
|
+
generated: undefined;
|
|
3806
|
+
}, {}, {}>;
|
|
3807
|
+
release: import("drizzle-orm/pg-core").PgColumn<{
|
|
3808
|
+
name: "release";
|
|
3809
|
+
tableName: "reporting_requests";
|
|
3810
|
+
dataType: "string";
|
|
3811
|
+
columnType: "PgText";
|
|
3812
|
+
data: string;
|
|
3813
|
+
driverParam: string;
|
|
3814
|
+
notNull: true;
|
|
3815
|
+
hasDefault: true;
|
|
3816
|
+
isPrimaryKey: false;
|
|
3817
|
+
isAutoincrement: false;
|
|
3818
|
+
hasRuntimeDefault: false;
|
|
3819
|
+
enumValues: [string, ...string[]];
|
|
3820
|
+
baseColumn: never;
|
|
3821
|
+
identity: undefined;
|
|
3822
|
+
generated: undefined;
|
|
3823
|
+
}, {}, {}>;
|
|
3824
|
+
requests: import("drizzle-orm/pg-core").PgColumn<{
|
|
3825
|
+
name: "requests";
|
|
3826
|
+
tableName: "reporting_requests";
|
|
3827
|
+
dataType: "number";
|
|
3828
|
+
columnType: "PgBigInt53";
|
|
3829
|
+
data: number;
|
|
3830
|
+
driverParam: string | number;
|
|
3831
|
+
notNull: true;
|
|
3832
|
+
hasDefault: true;
|
|
3833
|
+
isPrimaryKey: false;
|
|
3834
|
+
isAutoincrement: false;
|
|
3835
|
+
hasRuntimeDefault: false;
|
|
3836
|
+
enumValues: undefined;
|
|
3837
|
+
baseColumn: never;
|
|
3838
|
+
identity: undefined;
|
|
3839
|
+
generated: undefined;
|
|
3840
|
+
}, {}, {}>;
|
|
3841
|
+
errors: import("drizzle-orm/pg-core").PgColumn<{
|
|
3842
|
+
name: "errors";
|
|
3843
|
+
tableName: "reporting_requests";
|
|
3844
|
+
dataType: "number";
|
|
3845
|
+
columnType: "PgBigInt53";
|
|
3846
|
+
data: number;
|
|
3847
|
+
driverParam: string | number;
|
|
3848
|
+
notNull: true;
|
|
3849
|
+
hasDefault: true;
|
|
3850
|
+
isPrimaryKey: false;
|
|
3851
|
+
isAutoincrement: false;
|
|
3852
|
+
hasRuntimeDefault: false;
|
|
3853
|
+
enumValues: undefined;
|
|
3854
|
+
baseColumn: never;
|
|
3855
|
+
identity: undefined;
|
|
3856
|
+
generated: undefined;
|
|
3857
|
+
}, {}, {}>;
|
|
3858
|
+
};
|
|
3859
|
+
dialect: "pg";
|
|
3860
|
+
}>;
|
|
3471
3861
|
};
|
package/dist/tables.js
CHANGED
|
@@ -15,6 +15,8 @@ export const reportingEvents = pgTable('reporting_events', {
|
|
|
15
15
|
kindNs: text('kind_ns').generatedAlwaysAs(sql `split_part(kind, '.', 1)`),
|
|
16
16
|
site: text('site').notNull(),
|
|
17
17
|
environment: text('environment'),
|
|
18
|
+
release: text('release'),
|
|
19
|
+
color: text('color'),
|
|
18
20
|
tenantId: uuid('tenant_id'),
|
|
19
21
|
actorClass: text('actor_class'),
|
|
20
22
|
actorId: text('actor_id'),
|
|
@@ -91,6 +93,8 @@ export const reportingAnalytics = pgTable('reporting_analytics', {
|
|
|
91
93
|
.notNull()
|
|
92
94
|
.default(sql `'{}'::jsonb`),
|
|
93
95
|
schemaVersion: smallint('schema_version').notNull().default(1),
|
|
96
|
+
release: text('release'),
|
|
97
|
+
color: text('color'),
|
|
94
98
|
}, (t) => [
|
|
95
99
|
index('reporting_analytics_site_time_idx').on(t.site, t.occurredAt.desc(), t.id.desc()),
|
|
96
100
|
index('reporting_analytics_tenant_time_idx')
|
|
@@ -162,6 +166,7 @@ export const reportingErrors = pgTable('reporting_errors', {
|
|
|
162
166
|
stack: text('stack'),
|
|
163
167
|
runtime: text('runtime').notNull(),
|
|
164
168
|
release: text('release'),
|
|
169
|
+
color: text('color'),
|
|
165
170
|
firstSeenAt: timestamp('first_seen_at', { withTimezone: true }).notNull(),
|
|
166
171
|
lastSeenAt: timestamp('last_seen_at', { withTimezone: true }).notNull(),
|
|
167
172
|
occurrences: bigint('occurrences', { mode: 'number' }).notNull().default(1),
|
|
@@ -187,6 +192,20 @@ export const reportingErrorTenants = pgTable('reporting_error_tenants', {
|
|
|
187
192
|
primaryKey({ name: 'reporting_error_tenants_pkey', columns: [t.fingerprint, t.tenantId] }),
|
|
188
193
|
index('reporting_error_tenants_tenant_idx').on(t.tenantId, t.lastSeenAt.desc()),
|
|
189
194
|
]);
|
|
195
|
+
/** Request and 5xx counts per minute, color and release; mirrored from migrations/0010_requests.sql. */
|
|
196
|
+
export const reportingRequests = pgTable('reporting_requests', {
|
|
197
|
+
bucket: timestamp('bucket', { withTimezone: true }).notNull(),
|
|
198
|
+
site: text('site').notNull(),
|
|
199
|
+
color: text('color').notNull().default(''),
|
|
200
|
+
release: text('release').notNull().default(''),
|
|
201
|
+
requests: bigint('requests', { mode: 'number' }).notNull().default(0),
|
|
202
|
+
errors: bigint('errors', { mode: 'number' }).notNull().default(0),
|
|
203
|
+
}, (t) => [
|
|
204
|
+
primaryKey({
|
|
205
|
+
name: 'reporting_requests_pkey',
|
|
206
|
+
columns: [t.bucket, t.site, t.color, t.release],
|
|
207
|
+
}),
|
|
208
|
+
]);
|
|
190
209
|
export const tables = {
|
|
191
210
|
events: reportingEvents,
|
|
192
211
|
tasks: reportingTasks,
|
|
@@ -197,4 +216,5 @@ export const tables = {
|
|
|
197
216
|
analyticsWeekly: reportingAnalyticsWeekly,
|
|
198
217
|
errors: reportingErrors,
|
|
199
218
|
errorTenants: reportingErrorTenants,
|
|
219
|
+
requests: reportingRequests,
|
|
200
220
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -81,6 +81,8 @@ export interface ReportingOptions {
|
|
|
81
81
|
batchSize?: number;
|
|
82
82
|
/** Timer flush interval, ms. Default 1,000. */
|
|
83
83
|
flushEveryMs?: number;
|
|
84
|
+
/** How often counted requests are written, ms. Default 10,000. */
|
|
85
|
+
requestFlushEveryMs?: number;
|
|
84
86
|
}
|
|
85
87
|
export interface EventsPageOptions {
|
|
86
88
|
readonly limit?: number;
|
|
@@ -198,6 +200,11 @@ export interface Reporting {
|
|
|
198
200
|
readonly site: string;
|
|
199
201
|
readonly log: Logger;
|
|
200
202
|
readonly analytics: Analytics;
|
|
203
|
+
/** Counts one response for the release-health read; `flush` writes the counts. Never throws. */
|
|
204
|
+
readonly requests: {
|
|
205
|
+
record(status: number): void;
|
|
206
|
+
recordError(): void;
|
|
207
|
+
};
|
|
201
208
|
stats(): Stats;
|
|
202
209
|
}
|
|
203
210
|
export type { Actor, EventInput, FlatData, Level };
|
package/dist/writer.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { deployTags } from './deploy.js';
|
|
1
2
|
import { eventInputSchema } from './schema.js';
|
|
2
3
|
import { reportingEvents } from './tables.js';
|
|
3
4
|
export class Writer {
|
|
@@ -55,6 +56,7 @@ export class Writer {
|
|
|
55
56
|
kind: v.kind,
|
|
56
57
|
site: this.o.site,
|
|
57
58
|
environment: this.o.environment,
|
|
59
|
+
...deployTags(),
|
|
58
60
|
tenantId: v.tenantId ?? null,
|
|
59
61
|
actorClass: v.actor?.class ?? null,
|
|
60
62
|
actorId: v.actor?.id ?? null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/reporting",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "The estate's operational event log and housekeeping tick: a writer that never fails a request, a claim protocol that needs no scheduler, and the readers an operator opens. Per-app Postgres, host-supplied logger, framework-neutral core with a Next entry.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -26,10 +26,6 @@
|
|
|
26
26
|
"types": "./dist/next/index.d.ts",
|
|
27
27
|
"default": "./dist/next/index.js"
|
|
28
28
|
},
|
|
29
|
-
"./fake": {
|
|
30
|
-
"types": "./dist/fake/index.d.ts",
|
|
31
|
-
"default": "./dist/fake/index.js"
|
|
32
|
-
},
|
|
33
29
|
"./browser": {
|
|
34
30
|
"types": "./dist/browser/index.d.ts",
|
|
35
31
|
"default": "./dist/browser/index.js"
|
package/dist/fake/index.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { ReportingEventRow } from '../tables.js';
|
|
2
|
-
import type { AlertFinding, Logger, Reporting, TrackInput } from '../types.js';
|
|
3
|
-
/**
|
|
4
|
-
* An in-memory `Reporting` for a host's unit tests: same validation, same
|
|
5
|
-
* shape, no database. `rows` is the table; `alerts` is what `onAlert` would
|
|
6
|
-
* have received. Validation failures throw, as in development.
|
|
7
|
-
*/
|
|
8
|
-
export interface FakeReporting extends Reporting {
|
|
9
|
-
readonly rows: ReportingEventRow[];
|
|
10
|
-
readonly alerts: AlertFinding[];
|
|
11
|
-
/** What `analytics.track` received. */
|
|
12
|
-
readonly tracked: (TrackInput & {
|
|
13
|
-
site: string;
|
|
14
|
-
})[];
|
|
15
|
-
reset(): void;
|
|
16
|
-
}
|
|
17
|
-
export declare function createFakeReporting(opts?: {
|
|
18
|
-
site?: string;
|
|
19
|
-
log?: Logger;
|
|
20
|
-
redactEnvVars?: readonly string[];
|
|
21
|
-
environment?: string | null;
|
|
22
|
-
}): FakeReporting;
|