Skip to content

[Feature]: ActionLogRenderer — personality knob for Debug.action/error (CLEAR / MASKED / SILENT) #424

Description

@julienmerconsulting

Type Surface Impact Compat

Problem / motivation

Debug.action(...) and Debug.error(...) emit log lines that carry the verb (CLICK, TYPE, PASTE, KEY_DOWN, ...) followed by the exact arguments passed to the primitive — screen coordinates, typed text, keystroke sequences. That is the ideal debug surface at development time and a real privacy hazard the moment those logs are shipped to a central collector, an artifact server, or a customer-facing terminal.

Concrete cases that surfaced recently:

  • CI run captures TYPE("password-in-plain") in the archive, artifact stays on the build server for the retention window
  • Live demo on a client site with the console visible on the projected screen, verbose logs leaking business values in front of the audience
  • Long-running background OCR loops that flood stderr with thousands of identical HOVER(1234, 567) lines nobody wants to read

Today the only knob is verbosity (Debug level 0..5). Nothing at the shape or content level. Either the log is fully verbose or nothing at all, which is a binary that fits neither production CI nor demos.

Proposed solution

Introduce a small renderer org.sikuli.support.ActionLogRenderer that sits on the emission path of Debug.action() and Debug.error(), governed by a new Settings.ActionLogMode (case-insensitive String for max flexibility, defaults to "CLEAR"):

Mode Behaviour
CLEAR Vanilla, verbatim message and arguments. Same as today.
MASKED Keeps the verb (CLICK, TYPE, PASTE), replaces every argument value with ******. Verb-level observability preserved, zero value leakage.
SILENT Emits nothing. The log is at peace.

Wiring is a single injection point in each of Debug.action(...) and Debug.error(...):

String rendered = org.sikuli.support.ActionLogRenderer.render(
    message, args, Settings.ActionLogMode, /*isError=*/ false);
if (rendered == null) return;  // SILENT swallows the line
log(-1, actionPrefix, rendered);

Fail-safe by design: any rendering exception is swallowed and the raw message goes through — the log layer must never break the caller's script. SILENT returns null and the surrounding log(...) call is skipped entirely (no empty-line ghost in the stream).

Alternatives considered

  • Reuse Debug verbosity level (0..5) — solves nothing at the shape level. A Debug 5 line still contains the same sensitive args, just among more lines.
  • Redact at the log collector side — pushes the problem downstream, requires each customer to maintain a redaction rule set, and does not help live demos or scripts that never reach a collector.
  • Ship a @Sensitive annotation for individual args and mask by annotation — surgical but requires touching every primitive signature; over-engineered for the 90 % case where the answer is "mask everything in production, verbose everything in dev".

Where would this live?

  • API (Screen / Region / Pattern / Match)

API impact

Yes — new public API (backward-compatible):

  • Settings.ActionLogMode (public static field, defaults to "CLEAR")
  • org.sikuli.support.ActionLogRenderer.render(...) (public static utility method)

Callers of Debug.action(...) and Debug.error(...) do not change. Default behaviour is unchanged for any existing script that never touches the new field.

Use case / impact

  • Production CISettings.ActionLogMode = "MASKED" at the start of every run, no credentials in the archived logs, verb-level trace still available for post-mortem.
  • Live demo / client siteMASKED or SILENT depending on how much of the console the audience sees.
  • Long-running background OCR loopsSILENT on the noisy branch, promote back to CLEAR for the assertion-carrying calls only.
  • Dev / debugCLEAR stays the default, nothing changes.

Rollout is a one-line pin in an Init.java or equivalent bootstrap; downstream users who never set the field see the current behaviour byte for byte.

Additional context

The renderer is intentionally extensible — Mode.fromName(String) accepts any case-insensitive alias and the rendering path is table-driven, so future modes can be added without touching the wiring in Debug. What ships in v1 are the three documented modes above; the surface is designed to grow.

Rationale for String in Settings.ActionLogMode rather than an enum: Settings fields are read via reflection by several script runners (Jython, JRuby) and used from settings.properties; a String field roundtrips cleanly through all of them without a per-runner adapter, and Mode.fromName(...) handles the parse once at render time.

The three modes above are the ones I recommend for production. If you read the source, you will find more. Whether you use them in a demo is a matter of taste. 🤡

🦎

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Fields

    No fields configured for Feature.

    Projects

    Status
    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions