Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
61a6c44
feat: add sanitized diagnostics log bundle
christineyan4 May 27, 2026
45f9486
fix: keep log-tail diagnostics behind preview
christineyan4 May 27, 2026
81a1d6a
fix: address diagnostics bundle review comments
christineyan4 May 27, 2026
ba6f96b
chore: update dependencies
steipete Jun 8, 2026
6895bd5
Merge main into diagnostics bundle PR
christineyan4 Jun 11, 2026
9c6a484
Improve diagnostics export readability
christineyan4 Jun 11, 2026
e2ac882
Sanitize diagnostic logs before persistence
Jun 19, 2026
baf96c1
Merge remote-tracking branch 'upstream/master' into pr-562-error-logs
Jun 19, 2026
7ba4bda
Remove export-time diagnostics sanitization
Jun 19, 2026
4f8183d
Normalize nested diagnostics JSON before persistence
Jun 19, 2026
f8d5b61
Avoid escaped plus signs in diagnostics JSONL
Jun 22, 2026
33d5539
Normalize chat metadata cache persistence
Jun 22, 2026
ec053ea
Normalize setup logs before diagnostics bundling
Jun 22, 2026
7250fa1
Redact PII before diagnostics persistence
Jun 22, 2026
59c8547
Normalize remaining persisted PII diagnostics inputs
Jun 22, 2026
8ce2ad8
Stabilize diagnostics bundle preview
Jun 22, 2026
6c796b5
Open diagnostics preview without blocking UI
Jun 22, 2026
31294a5
Stabilize diagnostics preview sizing
Jun 22, 2026
a8fb9e3
Cache diagnostics bundle preview generation
Jun 25, 2026
604734f
Merge upstream/main into error logs
Jun 25, 2026
971a29c
Fix setup logger redaction regressions
Jun 25, 2026
1d8d3fd
Make diagnostics export read-only
Jun 25, 2026
17196ce
Harden diagnostics copy surfaces
Jun 26, 2026
f9900f9
Sanitize connection timeline copy
Jun 26, 2026
386bdb2
Address ClawSweeper diagnostics review
Jun 26, 2026
e1865e1
Fix diagnostics review follow-ups
Jun 26, 2026
8c9d487
Fix diagnostics redaction and freshness gaps
Jun 26, 2026
3258669
Fix diagnostics regression validation
Jun 26, 2026
802e44a
Fix ClawSweeper diagnostics blockers
Jun 26, 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
6 changes: 5 additions & 1 deletion src/OpenClaw.Connection/ConnectionDiagnostics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ public ConnectionDiagnostics(int capacity = 500, IClock? clock = null)

public void Record(string category, string message, string? detail = null)
{
var evt = new ConnectionDiagnosticEvent(_clock.UtcNow, category, message, detail);
var evt = new ConnectionDiagnosticEvent(
_clock.UtcNow,
TokenSanitizer.SanitizeLogMessage(category),
TokenSanitizer.SanitizeLogMessage(message),
detail is null ? null : TokenSanitizer.SanitizeLogMessage(detail));
lock (_lock)
{
_buffer[_head] = evt;
Expand Down
64 changes: 58 additions & 6 deletions src/OpenClaw.SetupEngine/SetupLogger.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
using System.Collections.Concurrent;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using OpenClaw.Shared;

namespace OpenClaw.SetupEngine;

Expand Down Expand Up @@ -84,7 +87,7 @@ private void Write(LogLevel level, string message, object? data = null)
{
if (level < _minLevel) return;

var sanitizedMessage = Sanitize(message);
var sanitizedMessage = NormalizeLogString(Sanitize(message));
var entry = new LogEntry(DateTimeOffset.UtcNow, _runId, level, sanitizedMessage, SanitizeData(data));
_recentEntries.Enqueue(entry);
while (_recentEntries.Count > MaxRecentEntries)
Expand Down Expand Up @@ -125,6 +128,10 @@ private void Write(LogLevel level, string message, object? data = null)
internal static string Sanitize(string input)
{
if (string.IsNullOrEmpty(input)) return input;
input = TokenSanitizer.SanitizeLogMessage(input);
if (input == TokenSanitizer.SanitizerTimeoutSentinel)
return input;

input = PrivateKeyPattern().Replace(input, "[REDACTED-PRIVATE-KEY]");
input = BearerPattern().Replace(input, "$1[REDACTED]");
input = JwtPattern().Replace(input, "[REDACTED-JWT]");
Expand All @@ -140,21 +147,65 @@ internal static string Sanitize(string input)
return null;

if (data is string value)
return Sanitize(value);
return NormalizeLogString(Sanitize(value));

try
{
var json = JsonSerializer.Serialize(data, _jsonOptions);
var sanitized = Sanitize(json);
using var doc = JsonDocument.Parse(sanitized);
return doc.RootElement.Clone();
var node = JsonNode.Parse(sanitized);
return NormalizeJsonNode(node);
}
catch (Exception ex) when (ex is JsonException or NotSupportedException or ArgumentException)
{
return Sanitize(data.ToString() ?? string.Empty);
return NormalizeLogString(Sanitize(data.ToString() ?? string.Empty));
}
}

private static JsonNode? NormalizeJsonNode(JsonNode? node)
{
switch (node)
{
case JsonObject obj:
var normalizedObject = new JsonObject();
foreach (var property in obj)
normalizedObject[property.Key] = NormalizeJsonNode(property.Value);
return normalizedObject;

case JsonArray array:
var normalizedArray = new JsonArray();
for (var i = 0; i < array.Count; i++)
normalizedArray.Add(NormalizeJsonNode(array[i]));
return normalizedArray;

case JsonValue value when value.TryGetValue<string>(out var text):
var normalized = NormalizeLogString(text);
var trimmed = normalized.TrimStart();
if (trimmed.StartsWith('{') || trimmed.StartsWith('['))
{
try
{
return NormalizeJsonNode(JsonNode.Parse(normalized));
}
catch (JsonException)
{
// Keep malformed JSON-shaped text as a flattened string.
}
}

return JsonValue.Create(normalized);

default:
return node?.DeepClone();
}
}

private static string NormalizeLogString(string value) =>
value
.Replace("\r\n", " ", StringComparison.Ordinal)
.Replace('\r', ' ')
.Replace('\n', ' ');

private static string Truncate(string input, int max = 4096)
=> input.Length <= max ? input : input[..max] + $"... [truncated {input.Length - max} chars]";

Expand All @@ -178,7 +229,8 @@ private static string Truncate(string input, int max = 4096)

private static readonly JsonSerializerOptions _jsonOptions = new()
{
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
};

public void Dispose() => _writer?.Dispose();
Expand Down
Loading
Loading