Skip to content

Commit 0961c47

Browse files
Merge pull request #268 from wttech/copilot/add-timestamped-output-methods
Add timestamped output methods to CodePrintStream
2 parents d789800 + 748a22c commit 0961c47

File tree

5 files changed

+219
-4
lines changed

5 files changed

+219
-4
lines changed

README.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ It works seamlessly across AEM on-premise, AMS, and AEMaaCS environments.
5757
- [Minimal example](#minimal-example)
5858
- [Inputs example](#inputs-example)
5959
- [Outputs example](#outputs-example)
60+
- [Console \& logging](#console--logging)
61+
- [Simple console output](#simple-console-output)
62+
- [Timestamped console output](#timestamped-console-output)
63+
- [Logged console output](#logged-console-output)
6064
- [ACL example](#acl-example)
6165
- [Repo example](#repo-example)
6266
- [Abortable example](#abortable-example)
@@ -350,6 +354,58 @@ outputs.text("configJson") {
350354
}
351355
```
352356

357+
#### Console & logging
358+
359+
Scripts provide three different ways to write messages to the console and logs, each serving different purposes:
360+
361+
##### Simple console output
362+
363+
Use `println` or `printf` for simple console output without timestamps or log levels. This is useful for quick debugging or generating simple text output.
364+
365+
```groovy
366+
void doRun() {
367+
println "Simple message without timestamp"
368+
printf "Formatted: %s = %d\n", "count", 42
369+
}
370+
```
371+
372+
##### Timestamped console output
373+
374+
Use `out.error()`, `out.warn()`, `out.info()`, `out.debug()`, `out.trace()` to write messages to the console with timestamps and log levels. These messages appear only in the execution console and are not persisted to AEM logs.
375+
376+
```groovy
377+
void doRun() {
378+
out.error "Failed to process resource: ${resource.path}"
379+
out.warn "Resource ${resource.path} is missing required property"
380+
out.info "Processing started"
381+
out.debug "Processing resource: ${resource.path}"
382+
out.trace "Entering method with params: ${params}"
383+
}
384+
```
385+
386+
##### Logged console output
387+
388+
Use `log.error()`, `log.warn()`, `log.info()`, `log.debug()`, `log.trace()` to write messages both to the console and to AEM logs (e.g., error.log). This is recommended for production scripts where you need persistent log records.
389+
390+
```groovy
391+
void doRun() {
392+
log.info "Doing regular stuff"
393+
394+
try {
395+
// ... risky logic
396+
log.info "Doing risky stuff ended"
397+
} catch (Exception e) {
398+
log.error "Doing risky stuff failed: ${e.message}", e
399+
}
400+
}
401+
```
402+
403+
**Best practices:**
404+
405+
- Use `println` / `printf` for quick debugging or simple text generation
406+
- Use `out.*` for console-only feedback during script execution (progress indicators, status updates)
407+
- Use `log.*` for important events that should be persisted in AEM logs (errors, warnings, critical operations)
408+
353409
#### ACL example
354410

355411
The following example of the automatic script demonstrates how to create a user and a group, assign permissions, and add members to the group using the [ACL service](https://github.com/wttech/acm/blob/main/core/src/main/java/dev/vml/es/acm/core/acl/Acl.java) (`acl`).
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dev.vml.es.acm.core.code;
2+
3+
import java.util.Arrays;
4+
import java.util.Optional;
5+
6+
public enum CodePrintLevel {
7+
INFO,
8+
ERROR,
9+
WARN,
10+
DEBUG,
11+
TRACE;
12+
13+
public static Optional<CodePrintLevel> find(String level) {
14+
return Arrays.stream(values())
15+
.filter(l -> l.name().equalsIgnoreCase(level))
16+
.findFirst();
17+
}
18+
19+
public static CodePrintLevel of(String level) {
20+
return find(level)
21+
.orElseThrow(() ->
22+
new IllegalArgumentException(String.format("Code print level '%s' is not supported!", level)));
23+
}
24+
}

core/src/main/java/dev/vml/es/acm/core/code/CodePrintStream.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,34 @@ public void fromLoggers(String... loggerNames) {
146146
public void fromLoggers(List<String> loggerNames) {
147147
loggerNames.forEach(this::fromLogger);
148148
}
149+
150+
public void info(String message) {
151+
printStamped(CodePrintLevel.INFO, message);
152+
}
153+
154+
public void error(String message) {
155+
printStamped(CodePrintLevel.ERROR, message);
156+
}
157+
158+
public void warn(String message) {
159+
printStamped(CodePrintLevel.WARN, message);
160+
}
161+
162+
public void debug(String message) {
163+
printStamped(CodePrintLevel.DEBUG, message);
164+
}
165+
166+
public void trace(String message) {
167+
printStamped(CodePrintLevel.TRACE, message);
168+
}
169+
170+
public void printStamped(String level, String message) {
171+
printStamped(CodePrintLevel.of(level), message);
172+
}
173+
174+
public void printStamped(CodePrintLevel level, String message) {
175+
LocalDateTime now = LocalDateTime.now();
176+
String timestamp = now.format(LOGGER_TIMESTAMP_FORMATTER);
177+
println(timestamp + " [" + level + "] " + message);
178+
}
149179
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package dev.vml.es.acm.core.code;
2+
3+
import static org.junit.jupiter.api.Assertions.assertTrue;
4+
5+
import ch.qos.logback.classic.LoggerContext;
6+
import java.io.ByteArrayOutputStream;
7+
import org.junit.jupiter.api.Test;
8+
import org.slf4j.LoggerFactory;
9+
10+
class CodePrintStreamTest {
11+
12+
@Test
13+
void shouldPrintInfoWithTimestamp() {
14+
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) {
15+
return;
16+
}
17+
18+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
19+
CodePrintStream printStream = new CodePrintStream(outputStream, "test-id");
20+
21+
printStream.info("Test info message");
22+
23+
String output = outputStream.toString();
24+
assertTrue(output.matches("\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[INFO\\] Test info message\\R"));
25+
}
26+
27+
@Test
28+
void shouldPrintErrorWithTimestamp() {
29+
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) {
30+
return;
31+
}
32+
33+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
34+
CodePrintStream printStream = new CodePrintStream(outputStream, "test-id");
35+
36+
printStream.error("Test error message");
37+
38+
String output = outputStream.toString();
39+
assertTrue(output.matches("\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[ERROR\\] Test error message\\R"));
40+
}
41+
42+
@Test
43+
void shouldPrintWarnWithTimestamp() {
44+
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) {
45+
return;
46+
}
47+
48+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
49+
CodePrintStream printStream = new CodePrintStream(outputStream, "test-id");
50+
51+
printStream.warn("Test warn message");
52+
53+
String output = outputStream.toString();
54+
assertTrue(output.matches("\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[WARN\\] Test warn message\\R"));
55+
}
56+
57+
@Test
58+
void shouldPrintDebugWithTimestamp() {
59+
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) {
60+
return;
61+
}
62+
63+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
64+
CodePrintStream printStream = new CodePrintStream(outputStream, "test-id");
65+
66+
printStream.debug("Test debug message");
67+
68+
String output = outputStream.toString();
69+
assertTrue(output.matches("\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[DEBUG\\] Test debug message\\R"));
70+
}
71+
72+
@Test
73+
void shouldPrintTraceWithTimestamp() {
74+
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) {
75+
return;
76+
}
77+
78+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
79+
CodePrintStream printStream = new CodePrintStream(outputStream, "test-id");
80+
81+
printStream.trace("Test trace message");
82+
83+
String output = outputStream.toString();
84+
assertTrue(output.matches("\\d{2}:\\d{2}:\\d{2}\\.\\d{3} \\[TRACE\\] Test trace message\\R"));
85+
}
86+
87+
@Test
88+
void shouldPrintMultipleMessagesWithDifferentLevels() {
89+
if (!(LoggerFactory.getILoggerFactory() instanceof LoggerContext)) {
90+
return;
91+
}
92+
93+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
94+
CodePrintStream printStream = new CodePrintStream(outputStream, "test-id");
95+
96+
printStream.info("Info message");
97+
printStream.error("Error message");
98+
printStream.warn("Warn message");
99+
100+
String output = outputStream.toString();
101+
assertTrue(output.contains("[INFO] Info message"));
102+
assertTrue(output.contains("[ERROR] Error message"));
103+
assertTrue(output.contains("[WARN] Warn message"));
104+
}
105+
}

ui.content/src/main/content/jcr_root/conf/acm/settings/snippet/available/core/general/demo_processing.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,17 @@ content: |
66
}
77
88
void doRun() {
9-
println "Processing..."
9+
out.info "Processing..."
1010
11-
println "Updating resources..."
11+
out.info "Updating resources..."
1212
def max = 20
1313
for (int i = 0; i < max; i++) {
1414
context.checkAborted()
1515
Thread.sleep(1000)
16-
println "Updated (\${i + 1}/\${max})"
16+
out.info "Updated (\${i + 1}/\${max})"
1717
}
1818
19-
println "Processing done"
19+
out.info "Processing done"
2020
}
2121
documentation: |
2222
A skeleton for a script that simulates a processing task.

0 commit comments

Comments
 (0)