All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.handler.admin.api.NodeLoggingAPI Maven / Gradle / Ivy

There is a newer version: 9.7.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.handler.admin.api;

import static org.apache.solr.client.solrj.impl.BinaryResponseParser.BINARY_CONTENT_TYPE_V2;
import static org.apache.solr.common.SolrException.ErrorCode.BAD_REQUEST;
import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_EDIT_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.CONFIG_READ_PERM;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.apache.solr.api.JerseyResource;
import org.apache.solr.client.api.model.SolrJerseyResponse;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrException;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.jersey.JacksonReflectMapWriter;
import org.apache.solr.jersey.PermissionName;
import org.apache.solr.logging.LogWatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

// TODO Add support for 'nodes' param once SOLR-16738 is completed.
/**
 * V2 APIs for getting or setting log levels on an individual node.
 *
 * 

These APIs ('/api/node/logging' and descendants) are analogous to the v1 /admin/info/logging. */ @Path("/node/logging") public class NodeLoggingAPI extends JerseyResource { private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); private final CoreContainer coreContainer; private final LogWatcher watcher; @Inject public NodeLoggingAPI(CoreContainer coreContainer) { this.coreContainer = coreContainer; this.watcher = coreContainer.getLogging(); } @Path("/levels") @GET @PermissionName(CONFIG_READ_PERM) @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) public ListLevelsResponse listAllLoggersAndLevels() { ensureLogWatcherEnabled(); final ListLevelsResponse response = instantiateLoggingResponse(ListLevelsResponse.class); response.levels = watcher.getAllLevels(); final List loggerInfo = watcher.getAllLoggers().stream() .sorted() .map(li -> new LogLevelInfo(li.getName(), li.getLevel(), li.isSet())) .collect(Collectors.toList()); response.loggers = loggerInfo; return response; } @Path("/levels") @PUT @PermissionName(CONFIG_EDIT_PERM) @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) public LoggingResponse modifyLocalLogLevel(List requestBody) { ensureLogWatcherEnabled(); final LoggingResponse response = instantiateLoggingResponse(LoggingResponse.class); if (requestBody == null) { throw new SolrException(BAD_REQUEST, "Missing request body"); } for (LogLevelChange change : requestBody) { watcher.setLogLevel(change.logger, change.level); } return response; } @Path("/messages") @GET @PermissionName(CONFIG_READ_PERM) @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) public LogMessagesResponse fetchLocalLogMessages(@QueryParam("since") Long boundingTimeMillis) { ensureLogWatcherEnabled(); final LogMessagesResponse response = instantiateLoggingResponse(LogMessagesResponse.class); if (boundingTimeMillis == null) { throw new SolrException(BAD_REQUEST, "Missing required parameter, 'since'."); } AtomicBoolean found = new AtomicBoolean(false); SolrDocumentList docs = watcher.getHistory(boundingTimeMillis, found); if (docs == null) { throw new SolrException(BAD_REQUEST, "History not enabled"); } final LogMessageInfo info = new LogMessageInfo(); if (boundingTimeMillis > 0) { info.boundingTimeMillis = boundingTimeMillis; info.found = found.get(); } else { info.levels = watcher.getAllLevels(); // show for the first request } info.lastRecordTimestampMillis = watcher.getLastEvent(); info.buffer = watcher.getHistorySize(); response.info = info; response.docs = docs; return response; } @Path("/messages/threshold") @PUT @PermissionName(CONFIG_EDIT_PERM) @Produces({"application/json", "application/xml", BINARY_CONTENT_TYPE_V2}) public LoggingResponse setMessageThreshold(SetThresholdRequestBody requestBody) { ensureLogWatcherEnabled(); final LoggingResponse response = instantiateLoggingResponse(LoggingResponse.class); if (requestBody == null || requestBody.level == null) { throw new SolrException(BAD_REQUEST, "Required parameter 'level' missing"); } watcher.setThreshold(requestBody.level); return response; } // A hacky testing-only parameter used to test the v1 LoggingHandler public static void writeLogsForTesting() { log.trace("trace message"); log.debug("debug message"); RuntimeException exc = new RuntimeException("test"); log.info("info (with exception) INFO", exc); log.warn("warn (with exception) WARN", exc); log.error("error (with exception) ERROR", exc); } private void ensureLogWatcherEnabled() { if (watcher == null) { throw new SolrException(BAD_REQUEST, "Logging Not Initialized"); } } private T instantiateLoggingResponse(Class clazz) { final T response = instantiateJerseyResponse(clazz); response.watcherName = watcher.getName(); return response; } /** Generic logging response that includes the name of the log watcher (e.g. "Log4j2") */ public static class LoggingResponse extends SolrJerseyResponse { @JsonProperty("watcher") public String watcherName; } /** A user-requested modification in the level that a specified logger reports at. */ public static class LogLevelChange implements JacksonReflectMapWriter { public LogLevelChange() {} public LogLevelChange(String logger, String level) { this.logger = logger; this.level = level; } @JsonProperty public String logger; @JsonProperty public String level; public static List createRequestBodyFromV1Params(String[] rawChangeValues) { final List changes = new ArrayList<>(); for (String rawChange : rawChangeValues) { String[] split = rawChange.split(":"); if (split.length != 2) { throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, "Invalid format, expected level:value, got " + rawChange); } changes.add(new LogLevelChange(split[0], split[1])); } return changes; } } /** The request body for the 'PUT /api/node/logging/messages/threshold' API. */ public static class SetThresholdRequestBody implements JacksonReflectMapWriter { public SetThresholdRequestBody() {} public SetThresholdRequestBody(String level) { this.level = level; } @JsonProperty(required = true) public String level; } /** Response format for the 'GET /api/node/logging/messages' API. */ public static class LogMessagesResponse extends LoggingResponse { @JsonProperty public LogMessageInfo info; @JsonProperty("history") public SolrDocumentList docs; } /** Metadata about the log messages returned by the 'GET /api/node/logging/messages' API */ public static class LogMessageInfo implements JacksonReflectMapWriter { @JsonProperty("since") public Long boundingTimeMillis; @JsonProperty public Boolean found; @JsonProperty public List levels; @JsonProperty("last") public long lastRecordTimestampMillis; @JsonProperty public int buffer; @JsonProperty public String threshold; } /** Response format for the 'GET /api/node/logging/levels' API. */ public static class ListLevelsResponse extends LoggingResponse { @JsonProperty public List levels; @JsonProperty public List loggers; } /** Representation of a single logger and its current state. */ public static class LogLevelInfo implements JacksonReflectMapWriter { public LogLevelInfo() {} public LogLevelInfo(String name, String level, boolean set) { this.name = name; this.level = level; this.set = set; } @JsonProperty("name") public String name; @JsonProperty("level") public String level; @JsonProperty("set") public boolean set; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy