Skip to content

Commit e5ed176

Browse files
author
João Araújo
committed
Refactor POST /logging-manager to support temporary log level via query param with duration in body; updated OpenAPI specs and README
1 parent 582ff78 commit e5ed176

File tree

3 files changed

+66
-74
lines changed

3 files changed

+66
-74
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,13 @@
1717
The **Quarkus Logging Manager** Extension provides you endpoints to visualize and manage the
1818
log level of your loggers.
1919

20-
| Endpoint | Http Method | Description |
21-
|----------------------------------------------|:-----------:|:----------------------------------------------------------------------------------------------------:|
22-
| `/q/logging-manager` | `GET` | Returns the list of all loggers, with information about the configured and effective level |
23-
| `/q/logging-manager?loggerName={loggerName}` | `GET` | Returns the logger specified by this name, with information about the configured and effective level |
24-
| `/q/logging-manager` | `POST` | Changes the log level of the specified logger |
25-
| `/q/logging-manager/levels` | `GET` | Get all the available level |
20+
| Endpoint | Http Method | Description |
21+
|----------------------------------------------|:-----------:|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------:|
22+
| `/q/logging-manager` | `GET` | Returns the list of all loggers, with information about the configured and effective level |
23+
| `/q/logging-manager?loggerName={loggerName}` | `GET` | Returns the logger specified by this name, with information about the configured and effective level |
24+
| `/q/logging-manager` | `POST` | Changes the log level of the specified logger |
25+
| `/q/logging-manager?temporary=true` | `POST` | Temporarily changes the log level of the specified logger for a given duration, then restores it to the original level |
26+
| `/q/logging-manager/levels` | `GET` | Get all the available level |
2627

2728
## Security
2829
Security of endpoints is important, and we do not want to allow unknown people to know (or worse, change!) the log levels of

deployment/src/main/java/io/quarkiverse/loggingmanager/deployment/LoggingManagerOpenAPIFilter.java

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,9 @@ public void filterOpenAPI(OpenAPI openAPI) {
5252
openAPI.addTag(tag);
5353
openAPI.getPaths()
5454
.addPathItem(basePath, createLoggersPathItem())
55-
.addPathItem(basePath + "/levels", createLevelsPathItem())
56-
.addPathItem(basePath + "/temporary-level", createTemporaryLevelPathItem());
55+
.addPathItem(basePath + "/levels", createLevelsPathItem());
5756
}
5857

59-
60-
6158
private Schema createLoggerLevel() {
6259
Schema schema = OASFactory.createSchema()
6360
.title("LoggerLevel")
@@ -109,10 +106,13 @@ private Operation createLoggerPostOperation() {
109106
Map<String, Schema> properties = new LinkedHashMap<>();
110107
properties.put("loggerName", OASFactory.createSchema().type(List.of(Schema.SchemaType.STRING)));
111108
properties.put("loggerLevel", OASFactory.createSchema().ref(REF_LOGGER_LEVEL));
109+
properties.put("duration", OASFactory.createSchema()
110+
.type(List.of(Schema.SchemaType.INTEGER))
111+
.description("Duration in seconds, used only if temporary=true"));
112112
return OASFactory.createOperation()
113113
.operationId("logging_manager_update")
114114
.summary("Update log level")
115-
.description("Update a log level for a certain logger")
115+
.description("Update a log level for a certain logger. Use query param `temporary=true` for temporary level.")
116116
.tags(Collections.singletonList(tag))
117117
.requestBody(OASFactory.createRequestBody()
118118
.content(OASFactory.createContent().addMediaType(
@@ -121,7 +121,10 @@ private Operation createLoggerPostOperation() {
121121
.type(List.of(Schema.SchemaType.OBJECT))
122122
.properties(properties)))))
123123
.responses(OASFactory.createAPIResponses()
124-
.addAPIResponse("201", OASFactory.createAPIResponse().description("Created")));
124+
.addAPIResponse("201", OASFactory.createAPIResponse().description("Created"))
125+
.addAPIResponse("200", OASFactory.createAPIResponse().description("Log level already set"))
126+
.addAPIResponse("400", OASFactory.createAPIResponse().description("Invalid request"))
127+
.addAPIResponse("404", OASFactory.createAPIResponse().description("Logger not found")));
125128
}
126129

127130
private PathItem createLevelsPathItem() {
@@ -143,37 +146,4 @@ private PathItem createLevelsPathItem() {
143146
.items(OASFactory.createSchema().ref(REF_LOGGER_LEVEL))))))));
144147
}
145148

146-
private PathItem createTemporaryLevelPathItem() {
147-
Map<String, Schema> properties = new LinkedHashMap<>();
148-
properties.put("loggerName", OASFactory.createSchema().type(List.of(Schema.SchemaType.STRING)));
149-
properties.put("loggerLevel", OASFactory.createSchema().ref(REF_LOGGER_LEVEL));
150-
properties.put("duration", OASFactory.createSchema()
151-
.type(List.of(Schema.SchemaType.INTEGER))
152-
.description("Duration in seconds before reverting to the previous level"));
153-
154-
return OASFactory.createPathItem()
155-
.summary("Set temporary log level duration")
156-
.description("Update a logger's level for a specified duration before reverting")
157-
.POST(OASFactory.createOperation()
158-
.operationId("logging_manager_set_duration")
159-
.summary("Set a temporary log level")
160-
.description("Set a log level for a specific logger that will automatically revert after a given duration")
161-
.tags(Collections.singletonList(tag))
162-
.requestBody(OASFactory.createRequestBody()
163-
.content(OASFactory.createContent().addMediaType(
164-
FORM_CONTENT_TYPE,
165-
OASFactory.createMediaType().schema(OASFactory.createSchema()
166-
.type(List.of(Schema.SchemaType.OBJECT))
167-
.properties(properties)))))
168-
.responses(OASFactory.createAPIResponses()
169-
.addAPIResponse("200", OASFactory.createAPIResponse()
170-
.description("Log level already set to the requested value"))
171-
.addAPIResponse("201", OASFactory.createAPIResponse()
172-
.description("Temporary log level set successfully"))
173-
.addAPIResponse("400", OASFactory.createAPIResponse()
174-
.description("Invalid request: invalid level or duration"))
175-
.addAPIResponse("404", OASFactory.createAPIResponse()
176-
.description("Logger not found"))));
177-
}
178-
179149
}

runtime/src/main/java/io/quarkiverse/loggingmanager/LoggerHandler.java

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import static io.vertx.core.http.HttpMethod.POST;
55

66
import java.util.Objects;
7-
87
import java.util.concurrent.ConcurrentHashMap;
8+
99
import org.jboss.logmanager.LogContext;
1010

1111
import io.vertx.core.Handler;
@@ -22,9 +22,19 @@ public class LoggerHandler implements Handler<RoutingContext> {
2222
private static final String LOGGER_NAME_PARAM = "loggerName";
2323
private static final String LOGGER_LEVEL_PARAM = "loggerLevel";
2424
private static final String LOGGER_DURATION = "duration";
25+
private static final String TEMPORARY_ENABLED = "temporary";
26+
27+
private final ConcurrentHashMap<String, TemporaryLogState> temporaryStates = new ConcurrentHashMap<>();
2528

26-
private final ConcurrentHashMap<String, Long> activeTimers = new ConcurrentHashMap<>();
29+
private static class TemporaryLogState {
30+
final String originalLevel;
31+
long timerId;
2732

33+
TemporaryLogState(String originalLevel, long timerId) {
34+
this.originalLevel = originalLevel;
35+
this.timerId = timerId;
36+
}
37+
}
2838

2939
/**
3040
* Handles incoming HTTP requests by delegating to appropriate method handlers.
@@ -47,8 +57,8 @@ public void handle(RoutingContext routingContext) {
4757
}
4858

4959
/**
50-
* Handles GET requests to retrieve logger information.
51-
* Returns all loggers if no logger name is specified, or details for a specific logger.
60+
* Handles GET requests to retrieve logger information. Returns all loggers if no logger name is specified, or
61+
* details for a specific logger.
5262
*
5363
* @param request The HTTP request
5464
* @param response The HTTP response
@@ -65,8 +75,8 @@ private void handleGet(HttpServerRequest request, HttpServerResponse response) {
6575
}
6676

6777
/**
68-
* Handles POST requests to update logger levels.
69-
* Updates the specified logger with the provided level or removes the level if none specified.
78+
* Handles POST requests to update logger levels. Updates the specified logger with the provided level or removes
79+
* the level if none specified.
7080
*
7181
* @param request The HTTP request
7282
* @param response The HTTP response
@@ -80,9 +90,11 @@ private void handlePost(HttpServerRequest request, HttpServerResponse response,
8090

8191
String loggerName = request.getFormAttribute(LOGGER_NAME_PARAM);
8292
String loggerLevel = request.getFormAttribute(LOGGER_LEVEL_PARAM);
83-
String loggerDuration = request.getFormAttribute(LOGGER_DURATION);
8493

85-
if (loggerDuration != null && !loggerDuration.isEmpty()) {
94+
String temporaryEnabled = request.getParam(TEMPORARY_ENABLED);
95+
96+
if ("true".equalsIgnoreCase(temporaryEnabled)) {
97+
String loggerDuration = request.getFormAttribute(LOGGER_DURATION);
8698
handleTemporaryPost(loggerName, loggerLevel, loggerDuration, routingContext, response);
8799
return;
88100
}
@@ -96,6 +108,16 @@ private void handlePost(HttpServerRequest request, HttpServerResponse response,
96108
response.setStatusCode(201).end();
97109
}
98110

111+
/**
112+
* Handles POST requests to update logger levels temporarily. Updates the specified logger to the provided level
113+
* for a given duration, then restores the original level automatically.
114+
*
115+
* @param loggerName The name of the logger to update
116+
* @param loggerLevel The new temporary log level
117+
* @param loggerDuration Duration in seconds for which the temporary level should apply
118+
* @param routingContext The Vert.x routing context
119+
* @param response The HTTP response
120+
*/
99121
private void handleTemporaryPost(
100122
String loggerName,
101123
String loggerLevel,
@@ -104,50 +126,49 @@ private void handleTemporaryPost(
104126
HttpServerResponse response) {
105127

106128
if (loggerLevel == null || !LogController.LEVELS.contains(loggerLevel.toUpperCase())) {
107-
response.setStatusCode(400).end("Invalid Logger level");
129+
response.setStatusCode(400).end("{\"error\":\"Invalid logger level\"}");
108130
return;
109131
}
110132

111133
if (!LogController.doesLoggerExist(loggerName)) {
112-
response.setStatusCode(404).end("Logger '" + loggerName + "' not found.");
134+
response.setStatusCode(404).end("{\"error\":\"Logger '" + loggerName + "' not found\"}");
113135
return;
114136
}
115137

116138
int durationSeconds;
117-
118139
try {
119140
durationSeconds = Integer.parseInt(loggerDuration);
141+
if (durationSeconds <= 0)
142+
throw new NumberFormatException();
120143
} catch (NumberFormatException e) {
121-
response.setStatusCode(400).end("Invalid duration");
144+
response.setStatusCode(400).end("{\"error\":\"Duration must be a positive integer\"}");
122145
return;
123146
}
124147

125-
if (durationSeconds <= 0) {
126-
response.setStatusCode(400).end("Duration must be positive");
148+
String currentLevel = LogController.getConfiguredLogLevel(LogContext.getLogContext().getLogger(loggerName));
149+
TemporaryLogState previousState = temporaryStates.get(loggerName);
150+
151+
if (loggerLevel.equalsIgnoreCase(currentLevel) && previousState == null) {
152+
response.setStatusCode(200).end("{\"message\":\"Log level already set to " + loggerLevel + "\"}");
127153
return;
128154
}
129155

130-
String oldLevel = LogController.getConfiguredLogLevel(
131-
LogContext.getLogContext().getLogger(loggerName));
132-
133-
if (oldLevel != null && oldLevel.equalsIgnoreCase(loggerLevel)) {
134-
response.setStatusCode(200).end("Log level already set to " + loggerLevel);
135-
return;
156+
if (previousState != null) {
157+
routingContext.vertx().cancelTimer(previousState.timerId);
136158
}
137159

138-
LogController.updateLogLevel(loggerName, loggerLevel);
160+
String originalLevel = previousState != null ? previousState.originalLevel : currentLevel;
139161

140-
Long previousTimerId = activeTimers.put(loggerName, null);
141-
if (previousTimerId != null) {
142-
routingContext.vertx().cancelTimer(previousTimerId);
143-
}
162+
LogController.updateLogLevel(loggerName, loggerLevel);
144163

145164
long timerId = routingContext.vertx().setTimer(durationSeconds * 1000L, id -> {
146-
LogController.updateLogLevel(loggerName, oldLevel);
147-
activeTimers.remove(loggerName, id);
165+
LogController.updateLogLevel(loggerName, originalLevel);
166+
temporaryStates.remove(loggerName);
148167
});
149168

150-
activeTimers.put(loggerName, timerId);
151-
response.setStatusCode(201).end("Temporary log level set for " + durationSeconds + " seconds");
169+
temporaryStates.put(loggerName, new TemporaryLogState(originalLevel, timerId));
170+
171+
response.setStatusCode(201)
172+
.end("{\"message\":\"Temporary log level '" + loggerLevel + "' set for " + durationSeconds + " seconds\"}");
152173
}
153174
}

0 commit comments

Comments
 (0)