@oneuptime/common 10.0.30 → 10.0.33

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.
Files changed (130) hide show
  1. package/Models/AnalyticsModels/ExceptionInstance.ts +29 -4
  2. package/Models/AnalyticsModels/Log.ts +110 -4
  3. package/Models/AnalyticsModels/Metric.ts +16 -9
  4. package/Models/AnalyticsModels/MonitorLog.ts +4 -2
  5. package/Models/AnalyticsModels/Span.ts +79 -6
  6. package/Models/DatabaseModels/Index.ts +8 -0
  7. package/Models/DatabaseModels/LogDropFilter.ts +480 -0
  8. package/Models/DatabaseModels/LogPipeline.ts +412 -0
  9. package/Models/DatabaseModels/LogPipelineProcessor.ts +430 -0
  10. package/Models/DatabaseModels/LogScrubRule.ts +516 -0
  11. package/Server/API/TelemetryAPI.ts +261 -0
  12. package/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.ts +131 -0
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.ts +79 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.ts +41 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.ts +57 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +4 -0
  17. package/Server/Services/AnalyticsDatabaseService.ts +61 -0
  18. package/Server/Services/LogAggregationService.ts +238 -1
  19. package/Server/Services/LogDropFilterService.ts +10 -0
  20. package/Server/Services/LogPipelineProcessorService.ts +10 -0
  21. package/Server/Services/LogPipelineService.ts +10 -0
  22. package/Server/Services/LogScrubRuleService.ts +10 -0
  23. package/Server/Services/TelemetryAttributeService.ts +4 -6
  24. package/Server/Utils/AnalyticsDatabase/Statement.ts +15 -1
  25. package/Server/Utils/AnalyticsDatabase/StatementGenerator.ts +126 -11
  26. package/Tests/Server/Services/LogAggregationService.test.ts +3 -2
  27. package/Types/AnalyticsDatabase/AnalyticsTableName.ts +9 -0
  28. package/Types/AnalyticsDatabase/TableColumnType.ts +4 -0
  29. package/Types/Date.ts +22 -0
  30. package/Types/Log/LogDropFilterAction.ts +6 -0
  31. package/Types/Log/LogPipelineProcessorType.ts +44 -0
  32. package/Types/Log/LogScrubAction.ts +7 -0
  33. package/Types/Log/LogScrubPatternType.ts +10 -0
  34. package/Types/Permission.ts +174 -0
  35. package/UI/Components/LogsViewer/LogsViewer.tsx +152 -4
  36. package/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.tsx +92 -0
  37. package/UI/Components/LogsViewer/components/LogDetailsPanel.tsx +332 -117
  38. package/UI/Components/LogsViewer/components/LogSearchBar.tsx +294 -274
  39. package/UI/Components/LogsViewer/components/LogsAnalyticsView.tsx +513 -234
  40. package/UI/Components/LogsViewer/components/LogsFilterCard.tsx +37 -29
  41. package/UI/Components/LogsViewer/components/LogsTable.tsx +6 -1
  42. package/UI/Components/LogsViewer/components/LogsViewerToolbar.tsx +106 -0
  43. package/UI/Utils/LogExport.ts +160 -0
  44. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js +28 -4
  45. package/build/dist/Models/AnalyticsModels/ExceptionInstance.js.map +1 -1
  46. package/build/dist/Models/AnalyticsModels/Log.js +97 -4
  47. package/build/dist/Models/AnalyticsModels/Log.js.map +1 -1
  48. package/build/dist/Models/AnalyticsModels/Metric.js +16 -9
  49. package/build/dist/Models/AnalyticsModels/Metric.js.map +1 -1
  50. package/build/dist/Models/AnalyticsModels/MonitorLog.js +4 -2
  51. package/build/dist/Models/AnalyticsModels/MonitorLog.js.map +1 -1
  52. package/build/dist/Models/AnalyticsModels/Span.js +73 -6
  53. package/build/dist/Models/AnalyticsModels/Span.js.map +1 -1
  54. package/build/dist/Models/DatabaseModels/Index.js +8 -0
  55. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  56. package/build/dist/Models/DatabaseModels/LogDropFilter.js +508 -0
  57. package/build/dist/Models/DatabaseModels/LogDropFilter.js.map +1 -0
  58. package/build/dist/Models/DatabaseModels/LogPipeline.js +438 -0
  59. package/build/dist/Models/DatabaseModels/LogPipeline.js.map +1 -0
  60. package/build/dist/Models/DatabaseModels/LogPipelineProcessor.js +452 -0
  61. package/build/dist/Models/DatabaseModels/LogPipelineProcessor.js.map +1 -0
  62. package/build/dist/Models/DatabaseModels/LogScrubRule.js +545 -0
  63. package/build/dist/Models/DatabaseModels/LogScrubRule.js.map +1 -0
  64. package/build/dist/Server/API/TelemetryAPI.js +155 -0
  65. package/build/dist/Server/API/TelemetryAPI.js.map +1 -1
  66. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.js +52 -0
  67. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773402621107-MigrationName.js.map +1 -0
  68. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js +34 -0
  69. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773414578773-MigrationName.js.map +1 -0
  70. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js +22 -0
  71. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773500000000-MigrationName.js.map +1 -0
  72. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.js +26 -0
  73. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1773676206197-MigrationName.js.map +1 -0
  74. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +4 -0
  75. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  76. package/build/dist/Server/Services/AnalyticsDatabaseService.js +30 -0
  77. package/build/dist/Server/Services/AnalyticsDatabaseService.js.map +1 -1
  78. package/build/dist/Server/Services/LogAggregationService.js +188 -1
  79. package/build/dist/Server/Services/LogAggregationService.js.map +1 -1
  80. package/build/dist/Server/Services/LogDropFilterService.js +9 -0
  81. package/build/dist/Server/Services/LogDropFilterService.js.map +1 -0
  82. package/build/dist/Server/Services/LogPipelineProcessorService.js +9 -0
  83. package/build/dist/Server/Services/LogPipelineProcessorService.js.map +1 -0
  84. package/build/dist/Server/Services/LogPipelineService.js +9 -0
  85. package/build/dist/Server/Services/LogPipelineService.js.map +1 -0
  86. package/build/dist/Server/Services/LogScrubRuleService.js +9 -0
  87. package/build/dist/Server/Services/LogScrubRuleService.js.map +1 -0
  88. package/build/dist/Server/Services/TelemetryAttributeService.js +4 -6
  89. package/build/dist/Server/Services/TelemetryAttributeService.js.map +1 -1
  90. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js +13 -1
  91. package/build/dist/Server/Utils/AnalyticsDatabase/Statement.js.map +1 -1
  92. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js +89 -2
  93. package/build/dist/Server/Utils/AnalyticsDatabase/StatementGenerator.js.map +1 -1
  94. package/build/dist/Tests/Server/Services/LogAggregationService.test.js +3 -2
  95. package/build/dist/Tests/Server/Services/LogAggregationService.test.js.map +1 -1
  96. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js +10 -0
  97. package/build/dist/Types/AnalyticsDatabase/AnalyticsTableName.js.map +1 -0
  98. package/build/dist/Types/AnalyticsDatabase/TableColumnType.js +4 -0
  99. package/build/dist/Types/AnalyticsDatabase/TableColumnType.js.map +1 -1
  100. package/build/dist/Types/Date.js +16 -0
  101. package/build/dist/Types/Date.js.map +1 -1
  102. package/build/dist/Types/Log/LogDropFilterAction.js +7 -0
  103. package/build/dist/Types/Log/LogDropFilterAction.js.map +1 -0
  104. package/build/dist/Types/Log/LogPipelineProcessorType.js +9 -0
  105. package/build/dist/Types/Log/LogPipelineProcessorType.js.map +1 -0
  106. package/build/dist/Types/Log/LogScrubAction.js +8 -0
  107. package/build/dist/Types/Log/LogScrubAction.js.map +1 -0
  108. package/build/dist/Types/Log/LogScrubPatternType.js +11 -0
  109. package/build/dist/Types/Log/LogScrubPatternType.js.map +1 -0
  110. package/build/dist/Types/Permission.js +152 -0
  111. package/build/dist/Types/Permission.js.map +1 -1
  112. package/build/dist/UI/Components/LogsViewer/LogsViewer.js +124 -11
  113. package/build/dist/UI/Components/LogsViewer/LogsViewer.js.map +1 -1
  114. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js +36 -0
  115. package/build/dist/UI/Components/LogsViewer/components/KeyboardShortcutsHelp.js.map +1 -0
  116. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js +114 -4
  117. package/build/dist/UI/Components/LogsViewer/components/LogDetailsPanel.js.map +1 -1
  118. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js +17 -5
  119. package/build/dist/UI/Components/LogsViewer/components/LogSearchBar.js.map +1 -1
  120. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js +229 -122
  121. package/build/dist/UI/Components/LogsViewer/components/LogsAnalyticsView.js.map +1 -1
  122. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js +5 -4
  123. package/build/dist/UI/Components/LogsViewer/components/LogsFilterCard.js.map +1 -1
  124. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js +4 -1
  125. package/build/dist/UI/Components/LogsViewer/components/LogsTable.js.map +1 -1
  126. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js +28 -0
  127. package/build/dist/UI/Components/LogsViewer/components/LogsViewerToolbar.js.map +1 -1
  128. package/build/dist/UI/Utils/LogExport.js +129 -0
  129. package/build/dist/UI/Utils/LogExport.js.map +1 -0
  130. package/package.json +1 -1
@@ -1,4 +1,11 @@
1
- import React, { FunctionComponent, ReactElement, useMemo } from "react";
1
+ import React, {
2
+ FunctionComponent,
3
+ ReactElement,
4
+ useCallback,
5
+ useEffect,
6
+ useMemo,
7
+ useState,
8
+ } from "react";
2
9
  import Log from "../../../../Models/AnalyticsModels/Log";
3
10
  import Service from "../../../../Models/DatabaseModels/Service";
4
11
  import Dictionary from "../../../../Types/Dictionary";
@@ -11,6 +18,15 @@ import Link from "../../Link/Link";
11
18
  import OneUptimeDate from "../../../../Types/Date";
12
19
  import JSONFunctions from "../../../../Types/JSONFunctions";
13
20
  import SeverityBadge from "./SeverityBadge";
21
+ import { JSONObject } from "../../../../Types/JSON";
22
+ import API from "../../../Utils/API/API";
23
+ import ModelAPI from "../../../Utils/ModelAPI/ModelAPI";
24
+ import { APP_API_URL } from "../../../Config";
25
+ import HTTPResponse from "../../../../Types/API/HTTPResponse";
26
+ import HTTPErrorResponse from "../../../../Types/API/HTTPErrorResponse";
27
+ import ObjectID from "../../../../Types/ObjectID";
28
+
29
+ type LogDetailTab = "details" | "context";
14
30
 
15
31
  export interface LogDetailsPanelProps {
16
32
  log: Log;
@@ -23,6 +39,7 @@ export interface LogDetailsPanelProps {
23
39
  | ((spanId: string, log: Log) => Route | URL | undefined)
24
40
  | undefined;
25
41
  variant?: "floating" | "embedded";
42
+ projectId?: ObjectID | undefined;
26
43
  }
27
44
 
28
45
  interface PreparedBody {
@@ -32,6 +49,14 @@ interface PreparedBody {
32
49
  raw: string;
33
50
  }
34
51
 
52
+ interface ContextLog {
53
+ id: string;
54
+ time: string;
55
+ severity: string;
56
+ body: string;
57
+ serviceId: string;
58
+ }
59
+
35
60
  const prepareBody: (body: string | undefined) => PreparedBody = (
36
61
  body: string | undefined,
37
62
  ): PreparedBody => {
@@ -45,7 +70,7 @@ const prepareBody: (body: string | undefined) => PreparedBody = (
45
70
  }
46
71
 
47
72
  try {
48
- const parsed: any = JSON.parse(body);
73
+ const parsed: unknown = JSON.parse(body);
49
74
  const pretty: string = JSON.stringify(parsed, null, 2);
50
75
  const compact: string = JSON.stringify(parsed);
51
76
  return {
@@ -64,9 +89,26 @@ const prepareBody: (body: string | undefined) => PreparedBody = (
64
89
  }
65
90
  };
66
91
 
92
+ function parseContextRow(row: JSONObject): ContextLog {
93
+ return {
94
+ id: String(row["_id"] || ""),
95
+ time: String(row["time"] || ""),
96
+ severity: String(row["severityText"] || "Unspecified"),
97
+ body: String(row["body"] || ""),
98
+ serviceId: String(row["serviceId"] || ""),
99
+ };
100
+ }
101
+
67
102
  const LogDetailsPanel: FunctionComponent<LogDetailsPanelProps> = (
68
103
  props: LogDetailsPanelProps,
69
104
  ): ReactElement => {
105
+ const [activeTab, setActiveTab] = useState<LogDetailTab>("details");
106
+ const [contextBefore, setContextBefore] = useState<Array<ContextLog>>([]);
107
+ const [contextAfter, setContextAfter] = useState<Array<ContextLog>>([]);
108
+ const [contextLoading, setContextLoading] = useState<boolean>(false);
109
+ const [contextError, setContextError] = useState<string>("");
110
+ const [contextLoaded, setContextLoaded] = useState<boolean>(false);
111
+
70
112
  const variant: "floating" | "embedded" = props.variant || "floating";
71
113
  const serviceId: string = props.log.serviceId?.toString() || "";
72
114
  const service: Service | undefined = props.serviceMap[serviceId];
@@ -137,6 +179,71 @@ const LogDetailsPanel: FunctionComponent<LogDetailsPanelProps> = (
137
179
  return undefined;
138
180
  }, [spanId, props, traceId]);
139
181
 
182
+ const loadContext: () => Promise<void> =
183
+ useCallback(async (): Promise<void> => {
184
+ if (!props.projectId || !serviceId || !props.log.time) {
185
+ setContextError("Missing project or service information for context.");
186
+ return;
187
+ }
188
+
189
+ try {
190
+ setContextLoading(true);
191
+ setContextError("");
192
+
193
+ const response: HTTPResponse<JSONObject> | HTTPErrorResponse =
194
+ await API.post({
195
+ url: URL.fromString(APP_API_URL.toString()).addRoute(
196
+ "/telemetry/logs/context",
197
+ ),
198
+ data: {
199
+ logId: props.log.getColumnValue("_id")?.toString() || "",
200
+ serviceId: serviceId,
201
+ time: props.log.time
202
+ ? OneUptimeDate.toString(props.log.time)
203
+ : "",
204
+ count: 5,
205
+ },
206
+ headers: {
207
+ ...ModelAPI.getCommonHeaders(),
208
+ },
209
+ });
210
+
211
+ if (response instanceof HTTPErrorResponse) {
212
+ throw response;
213
+ }
214
+
215
+ const before: Array<JSONObject> =
216
+ (response.data["before"] as Array<JSONObject>) || [];
217
+ const after: Array<JSONObject> =
218
+ (response.data["after"] as Array<JSONObject>) || [];
219
+
220
+ setContextBefore(before.map(parseContextRow));
221
+ setContextAfter(after.map(parseContextRow));
222
+ setContextLoaded(true);
223
+ } catch (err) {
224
+ setContextError(
225
+ `Failed to load log context. ${API.getFriendlyErrorMessage(err as Error)}`,
226
+ );
227
+ } finally {
228
+ setContextLoading(false);
229
+ }
230
+ }, [props.projectId, serviceId, props.log]);
231
+
232
+ useEffect(() => {
233
+ if (activeTab === "context" && !contextLoaded && !contextLoading) {
234
+ void loadContext();
235
+ }
236
+ }, [activeTab, contextLoaded, contextLoading, loadContext]);
237
+
238
+ // Reset context when log changes
239
+ useEffect(() => {
240
+ setContextLoaded(false);
241
+ setContextBefore([]);
242
+ setContextAfter([]);
243
+ setContextError("");
244
+ setActiveTab("details");
245
+ }, [props.log]);
246
+
140
247
  const containerClassName: string =
141
248
  variant === "embedded"
142
249
  ? "rounded-lg border border-gray-200 bg-white p-5 shadow-sm"
@@ -149,6 +256,47 @@ const LogDetailsPanel: FunctionComponent<LogDetailsPanelProps> = (
149
256
  const smallBadgeClass: string =
150
257
  "inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-50 px-2 py-1 text-[11px] font-mono uppercase tracking-wide text-gray-600";
151
258
 
259
+ const tabClass: (isActive: boolean) => string = (
260
+ isActive: boolean,
261
+ ): string => {
262
+ return `px-3 py-1.5 text-xs font-medium rounded-md transition-colors ${
263
+ isActive
264
+ ? "bg-indigo-50 text-indigo-700 border border-indigo-200"
265
+ : "text-gray-600 hover:text-gray-800 hover:bg-gray-50 border border-transparent"
266
+ }`;
267
+ };
268
+
269
+ const renderContextLogRow: (
270
+ ctxLog: ContextLog,
271
+ isCurrent: boolean,
272
+ ) => ReactElement = (
273
+ ctxLog: ContextLog,
274
+ isCurrent: boolean,
275
+ ): ReactElement => {
276
+ const rowClass: string = isCurrent
277
+ ? "border-l-2 border-l-indigo-500 bg-indigo-50"
278
+ : "border-l-2 border-l-transparent hover:bg-gray-50";
279
+
280
+ return (
281
+ <div
282
+ key={ctxLog.id || ctxLog.time}
283
+ className={`flex items-start gap-3 px-3 py-2 ${rowClass}`}
284
+ >
285
+ <span className="flex-none whitespace-nowrap font-mono text-[11px] text-gray-400">
286
+ {ctxLog.time
287
+ ? OneUptimeDate.getDateAsUserFriendlyFormattedString(
288
+ new Date(ctxLog.time),
289
+ )
290
+ : "-"}
291
+ </span>
292
+ <SeverityBadge severity={ctxLog.severity} />
293
+ <span className="min-w-0 flex-1 truncate font-mono text-xs text-gray-700">
294
+ {ctxLog.body.slice(0, 200) || "-"}
295
+ </span>
296
+ </div>
297
+ );
298
+ };
299
+
152
300
  return (
153
301
  <div className={containerClassName}>
154
302
  <div
@@ -204,134 +352,201 @@ const LogDetailsPanel: FunctionComponent<LogDetailsPanelProps> = (
204
352
  )}
205
353
  </div>
206
354
 
207
- <div className="mt-4 space-y-5 text-sm text-gray-700">
208
- <section className="space-y-3">
209
- <header className="flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
210
- <span>Log Body</span>
211
- <CopyTextButton
212
- textToBeCopied={bodyDetails.raw}
213
- size="xs"
214
- variant="ghost"
215
- iconOnly={false}
216
- title="Copy log body"
217
- />
218
- </header>
219
-
220
- <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
221
- {bodyDetails.isJson ? (
222
- <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words font-mono text-[13px] leading-6 text-gray-800">
223
- {bodyDetails.pretty}
224
- </pre>
225
- ) : (
226
- <p className="whitespace-pre-wrap break-words font-mono text-[13px] leading-6 text-gray-800">
227
- {bodyDetails.pretty || "-"}
228
- </p>
229
- )}
230
- </div>
231
- </section>
232
-
233
- {(traceId || spanId) && (
234
- <section className="grid gap-4 md:grid-cols-2">
235
- {traceId && (
236
- <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
237
- <div className="mb-2 flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
238
- <span>Trace ID</span>
239
- <CopyTextButton
240
- textToBeCopied={traceId}
241
- size="xs"
242
- variant="ghost"
243
- iconOnly={true}
244
- title="Copy trace id"
245
- />
246
- </div>
247
- <div className="flex items-center justify-between gap-2">
248
- {traceRoute ? (
249
- <Link
250
- to={traceRoute}
251
- className="max-w-full truncate font-mono text-xs text-indigo-600 hover:text-indigo-500"
252
- title={`View trace ${traceId}`}
253
- >
254
- {traceId}
255
- </Link>
256
- ) : (
257
- <span
258
- className="max-w-full truncate font-mono text-xs text-gray-700"
259
- title={traceId}
260
- >
261
- {traceId}
262
- </span>
263
- )}
264
- {traceRoute && (
265
- <Icon
266
- icon={IconProp.ExternalLink}
267
- className="h-4 w-4 flex-none text-indigo-400"
268
- />
269
- )}
270
- </div>
271
- </div>
272
- )}
273
-
274
- {spanId && (
275
- <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
276
- <div className="mb-2 flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
277
- <span>Span ID</span>
278
- <CopyTextButton
279
- textToBeCopied={spanId}
280
- size="xs"
281
- variant="ghost"
282
- iconOnly={true}
283
- title="Copy span id"
284
- />
285
- </div>
286
- <div className="flex items-center justify-between gap-2">
287
- {spanRoute ? (
288
- <Link
289
- to={spanRoute}
290
- className="max-w-full truncate font-mono text-xs text-indigo-600 hover:text-indigo-500"
291
- title={`View span ${spanId}`}
292
- >
293
- {spanId}
294
- </Link>
295
- ) : (
296
- <span
297
- className="max-w-full truncate font-mono text-xs text-gray-700"
298
- title={spanId}
299
- >
300
- {spanId}
301
- </span>
302
- )}
303
- {spanRoute && (
304
- <Icon
305
- icon={IconProp.ExternalLink}
306
- className="h-4 w-4 flex-none text-indigo-400"
307
- />
308
- )}
309
- </div>
310
- </div>
311
- )}
312
- </section>
355
+ {/* Tab bar */}
356
+ <div className="mt-3 flex items-center gap-1">
357
+ <button
358
+ type="button"
359
+ className={tabClass(activeTab === "details")}
360
+ onClick={() => {
361
+ setActiveTab("details");
362
+ }}
363
+ >
364
+ Details
365
+ </button>
366
+ {props.projectId && (
367
+ <button
368
+ type="button"
369
+ className={tabClass(activeTab === "context")}
370
+ onClick={() => {
371
+ setActiveTab("context");
372
+ }}
373
+ >
374
+ Context
375
+ </button>
313
376
  )}
377
+ </div>
314
378
 
315
- {prettyAttributes && (
379
+ {/* Tab content */}
380
+ {activeTab === "details" && (
381
+ <div className="mt-4 space-y-5 text-sm text-gray-700">
316
382
  <section className="space-y-3">
317
383
  <header className="flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
318
- <span>Attributes</span>
384
+ <span>Log Body</span>
319
385
  <CopyTextButton
320
- textToBeCopied={prettyAttributes}
386
+ textToBeCopied={bodyDetails.raw}
321
387
  size="xs"
322
388
  variant="ghost"
323
389
  iconOnly={false}
324
- title="Copy attributes"
390
+ title="Copy log body"
325
391
  />
326
392
  </header>
393
+
327
394
  <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
328
- <pre className="max-h-72 overflow-auto whitespace-pre-wrap break-words font-mono text-[13px] leading-6 text-gray-800">
329
- {prettyAttributes}
330
- </pre>
395
+ {bodyDetails.isJson ? (
396
+ <pre className="max-h-80 overflow-auto whitespace-pre-wrap break-words font-mono text-[13px] leading-6 text-gray-800">
397
+ {bodyDetails.pretty}
398
+ </pre>
399
+ ) : (
400
+ <p className="whitespace-pre-wrap break-words font-mono text-[13px] leading-6 text-gray-800">
401
+ {bodyDetails.pretty || "-"}
402
+ </p>
403
+ )}
331
404
  </div>
332
405
  </section>
333
- )}
334
- </div>
406
+
407
+ {(traceId || spanId) && (
408
+ <section className="grid gap-4 md:grid-cols-2">
409
+ {traceId && (
410
+ <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
411
+ <div className="mb-2 flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
412
+ <span>Trace ID</span>
413
+ <CopyTextButton
414
+ textToBeCopied={traceId}
415
+ size="xs"
416
+ variant="ghost"
417
+ iconOnly={true}
418
+ title="Copy trace id"
419
+ />
420
+ </div>
421
+ <div className="flex items-center justify-between gap-2">
422
+ {traceRoute ? (
423
+ <Link
424
+ to={traceRoute}
425
+ className="max-w-full truncate font-mono text-xs text-indigo-600 hover:text-indigo-500"
426
+ title={`View trace ${traceId}`}
427
+ >
428
+ {traceId}
429
+ </Link>
430
+ ) : (
431
+ <span
432
+ className="max-w-full truncate font-mono text-xs text-gray-700"
433
+ title={traceId}
434
+ >
435
+ {traceId}
436
+ </span>
437
+ )}
438
+ {traceRoute && (
439
+ <Icon
440
+ icon={IconProp.ExternalLink}
441
+ className="h-4 w-4 flex-none text-indigo-400"
442
+ />
443
+ )}
444
+ </div>
445
+ </div>
446
+ )}
447
+
448
+ {spanId && (
449
+ <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
450
+ <div className="mb-2 flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
451
+ <span>Span ID</span>
452
+ <CopyTextButton
453
+ textToBeCopied={spanId}
454
+ size="xs"
455
+ variant="ghost"
456
+ iconOnly={true}
457
+ title="Copy span id"
458
+ />
459
+ </div>
460
+ <div className="flex items-center justify-between gap-2">
461
+ {spanRoute ? (
462
+ <Link
463
+ to={spanRoute}
464
+ className="max-w-full truncate font-mono text-xs text-indigo-600 hover:text-indigo-500"
465
+ title={`View span ${spanId}`}
466
+ >
467
+ {spanId}
468
+ </Link>
469
+ ) : (
470
+ <span
471
+ className="max-w-full truncate font-mono text-xs text-gray-700"
472
+ title={spanId}
473
+ >
474
+ {spanId}
475
+ </span>
476
+ )}
477
+ {spanRoute && (
478
+ <Icon
479
+ icon={IconProp.ExternalLink}
480
+ className="h-4 w-4 flex-none text-indigo-400"
481
+ />
482
+ )}
483
+ </div>
484
+ </div>
485
+ )}
486
+ </section>
487
+ )}
488
+
489
+ {prettyAttributes && (
490
+ <section className="space-y-3">
491
+ <header className="flex items-center justify-between text-[11px] uppercase tracking-wide text-gray-400">
492
+ <span>Attributes</span>
493
+ <CopyTextButton
494
+ textToBeCopied={prettyAttributes}
495
+ size="xs"
496
+ variant="ghost"
497
+ iconOnly={false}
498
+ title="Copy attributes"
499
+ />
500
+ </header>
501
+ <div className={`rounded-lg border ${surfaceCardClass} p-4`}>
502
+ <pre className="max-h-72 overflow-auto whitespace-pre-wrap break-words font-mono text-[13px] leading-6 text-gray-800">
503
+ {prettyAttributes}
504
+ </pre>
505
+ </div>
506
+ </section>
507
+ )}
508
+ </div>
509
+ )}
510
+
511
+ {activeTab === "context" && (
512
+ <div className="mt-4 text-sm text-gray-700">
513
+ {contextLoading && (
514
+ <div className="flex items-center justify-center py-8 text-xs text-gray-400">
515
+ Loading surrounding logs...
516
+ </div>
517
+ )}
518
+ {contextError && (
519
+ <div className="rounded-md bg-red-50 p-3 text-xs text-red-600">
520
+ {contextError}
521
+ </div>
522
+ )}
523
+ {!contextLoading && !contextError && contextLoaded && (
524
+ <div className="divide-y divide-gray-100 rounded-lg border border-gray-200">
525
+ {contextBefore.length === 0 && contextAfter.length === 0 && (
526
+ <div className="px-3 py-6 text-center text-xs text-gray-400">
527
+ No surrounding logs found for this service.
528
+ </div>
529
+ )}
530
+ {contextBefore.map((ctxLog: ContextLog) => {
531
+ return renderContextLogRow(ctxLog, false);
532
+ })}
533
+ {renderContextLogRow(
534
+ {
535
+ id: props.log.getColumnValue("_id")?.toString() || "current",
536
+ time: props.log.time ? props.log.time.toString() : "",
537
+ severity: props.log.severityText?.toString() || "Unspecified",
538
+ body: props.log.body || "",
539
+ serviceId: serviceId,
540
+ },
541
+ true,
542
+ )}
543
+ {contextAfter.map((ctxLog: ContextLog) => {
544
+ return renderContextLogRow(ctxLog, false);
545
+ })}
546
+ </div>
547
+ )}
548
+ </div>
549
+ )}
335
550
  </div>
336
551
  );
337
552
  };