Skip to content

Commit

Permalink
Adding ability to update logger level
Browse files Browse the repository at this point in the history
  • Loading branch information
xiangfu0 committed Aug 9, 2022
1 parent 449f89a commit 5395c67
Show file tree
Hide file tree
Showing 7 changed files with 639 additions and 0 deletions.
@@ -0,0 +1,83 @@
/**
* 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.pinot.broker.api.resources;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiKeyAuthDefinition;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.Authorization;
import io.swagger.annotations.SecurityDefinition;
import io.swagger.annotations.SwaggerDefinition;
import java.util.List;
import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.pinot.common.utils.LoggerUtils;

import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY;


/**
* Logger resource.
*/
@Api(tags = "Logger", authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY)})
@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = @ApiKeyAuthDefinition(name =
HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = SWAGGER_AUTHORIZATION_KEY)))
@Path("/")
public class PinotBrokerLogger {

@GET
@Path("/loggers")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get all the loggers", notes = "Return all the logger names")
public List<String> getLoggers() {
return LoggerUtils.getAllLoggers();
}

@GET
@Path("/loggers/{loggerName}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get logger configs", notes = "Return logger info")
public Map<String, String> getLogger(
@ApiParam(value = "Logger name", required = true) @PathParam("loggerName") String loggerName) {
Map<String, String> loggerInfo = LoggerUtils.getLoggerInfo(loggerName);
if (loggerInfo == null) {
throw new WebApplicationException(String.format("Logger %s not found", loggerName), Response.Status.NOT_FOUND);
}
return loggerInfo;
}

@PUT
@Path("/loggers/{loggerName}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Set logger level", notes = "Set logger level for a given logger")
public Map<String, String> setLoggerLevel(@ApiParam(value = "Logger name") @PathParam("loggerName") String loggerName,
@ApiParam(value = "Logger level") @QueryParam("level") String level) {
return LoggerUtils.setLoggerLevel(loggerName, level);
}
}
@@ -0,0 +1,106 @@
/**
* 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.pinot.common.utils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Nullable;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;


/**
* Logger utils for process level logger management.
*/
public class LoggerUtils {
private static final String ROOT = "root";
private static final String NAME = "name";
private static final String LEVEL = "level";
private static final String FILTER = "filter";

private LoggerUtils() {
}

/**
* Set logger level at runtime.
* @param loggerName
* @param logLevel
* @return logger info
*/
public static Map<String, String> setLoggerLevel(String loggerName, String logLevel) {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = context.getConfiguration();
if (!getAllLoggers().contains(loggerName)) {
throw new RuntimeException("Logger - " + loggerName + " not found");
}
LoggerConfig loggerConfig = getLoggerConfig(config, loggerName);
try {
loggerConfig.setLevel(Level.valueOf(logLevel));
} catch (Exception e) {
throw new RuntimeException("Unrecognized logger level - " + logLevel, e);
}
// This causes all Loggers to re-fetch information from their LoggerConfig.
context.updateLoggers();
return getLoggerResponse(loggerConfig);
}

/**
* Fetch logger info of name, level and filter.
* @param loggerName
* @return logger info
*/
@Nullable
public static Map<String, String> getLoggerInfo(String loggerName) {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = context.getConfiguration();
if (!getAllLoggers().contains(loggerName)) {
return null;
}
LoggerConfig loggerConfig = getLoggerConfig(config, loggerName);
return getLoggerResponse(loggerConfig);
}

/**
* @return a list of all the logger names
*/
public static List<String> getAllLoggers() {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = context.getConfiguration();
return config.getLoggers().values().stream().map(LoggerConfig::toString).collect(Collectors.toList());
}

private static LoggerConfig getLoggerConfig(Configuration config, String loggerName) {
return loggerName.equalsIgnoreCase(ROOT) ? config.getRootLogger() : config.getLoggerConfig(loggerName);
}

private static Map<String, String> getLoggerResponse(LoggerConfig loggerConfig) {
Map<String, String> result = new HashMap<>();
result.put(NAME, loggerConfig.toString());
result.put(LEVEL, loggerConfig.getLevel().name());
Filter filter = loggerConfig.getFilter();
result.put(FILTER, filter == null ? null : filter.toString());
return result;
}
}
@@ -0,0 +1,118 @@
/**
* 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.pinot.common.utils;

import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;

import static org.testng.Assert.*;


public class LoggerUtilsTest {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggerUtilsTest.class);

@Test
public void testGetAllLoggers() {
List<String> allLoggers = LoggerUtils.getAllLoggers();
assertEquals(allLoggers.size(), 1);
assertEquals(allLoggers.get(0), "root");
}

@Test
public void testGetLoggerInfo() {
Map<String, String> rootLoggerInfo = LoggerUtils.getLoggerInfo("root");
assertEquals(rootLoggerInfo.get("name"), "root");
assertEquals(rootLoggerInfo.get("level"), "WARN");
assertNull(rootLoggerInfo.get("filter"));

assertNull(LoggerUtils.getLoggerInfo("notExistLogger"));
}

@Test
public void testChangeLoggerLevel() {
assertEquals(LoggerUtils.getLoggerInfo("root").get("level"), "WARN");
for (String level : ImmutableList.of("WARN", "INFO", "DEBUG", "ERROR", "WARN")) {
LoggerUtils.setLoggerLevel("root", level);
checkLogLevel(level);
assertEquals(LoggerUtils.getLoggerInfo("root").get("level"), level);
}
}

@Test
public void testChangeLoggerLevelWithExceptions() {
try {
LoggerUtils.setLoggerLevel("notExistLogger", "INFO");
fail("Shouldn't reach here");
} catch (RuntimeException e) {
assertEquals(e.getMessage(), "Logger - notExistLogger not found");
}
try {
LoggerUtils.setLoggerLevel("root", "NotALevel");
fail("Shouldn't reach here");
} catch (RuntimeException e) {
assertEquals(e.getMessage(), "Unrecognized logger level - NotALevel");
}
}

private void checkLogLevel(String level) {
switch (level) {
case "ERROR":
assertTrue(LOGGER.isErrorEnabled());
assertFalse(LOGGER.isWarnEnabled());
assertFalse(LOGGER.isInfoEnabled());
assertFalse(LOGGER.isDebugEnabled());
assertFalse(LOGGER.isTraceEnabled());
break;
case "WARN":
assertTrue(LOGGER.isErrorEnabled());
assertTrue(LOGGER.isWarnEnabled());
assertFalse(LOGGER.isInfoEnabled());
assertFalse(LOGGER.isDebugEnabled());
assertFalse(LOGGER.isTraceEnabled());
break;
case "INFO":
assertTrue(LOGGER.isErrorEnabled());
assertTrue(LOGGER.isWarnEnabled());
assertTrue(LOGGER.isInfoEnabled());
assertFalse(LOGGER.isDebugEnabled());
assertFalse(LOGGER.isTraceEnabled());
break;
case "DEBUG":
assertTrue(LOGGER.isErrorEnabled());
assertTrue(LOGGER.isWarnEnabled());
assertTrue(LOGGER.isInfoEnabled());
assertTrue(LOGGER.isDebugEnabled());
assertFalse(LOGGER.isTraceEnabled());
break;
case "TRACE":
assertTrue(LOGGER.isErrorEnabled());
assertTrue(LOGGER.isWarnEnabled());
assertTrue(LOGGER.isInfoEnabled());
assertTrue(LOGGER.isDebugEnabled());
assertTrue(LOGGER.isTraceEnabled());
break;
default:
break;
}
}
}
@@ -0,0 +1,83 @@
/**
* 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.pinot.controller.api.resources;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiKeyAuthDefinition;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.Authorization;
import io.swagger.annotations.SecurityDefinition;
import io.swagger.annotations.SwaggerDefinition;
import java.util.List;
import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.pinot.common.utils.LoggerUtils;

import static org.apache.pinot.spi.utils.CommonConstants.SWAGGER_AUTHORIZATION_KEY;


/**
* Logger resource.
*/
@Api(tags = "Logger", authorizations = {@Authorization(value = SWAGGER_AUTHORIZATION_KEY)})
@SwaggerDefinition(securityDefinition = @SecurityDefinition(apiKeyAuthDefinitions = @ApiKeyAuthDefinition(name =
HttpHeaders.AUTHORIZATION, in = ApiKeyAuthDefinition.ApiKeyLocation.HEADER, key = SWAGGER_AUTHORIZATION_KEY)))
@Path("/")
public class PinotControllerLogger {

@GET
@Path("/loggers")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get all the loggers", notes = "Return all the logger names")
public List<String> getLoggers() {
return LoggerUtils.getAllLoggers();
}

@GET
@Path("/loggers/{loggerName}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Get logger configs", notes = "Return logger info")
public Map<String, String> getLogger(
@ApiParam(value = "Logger name", required = true) @PathParam("loggerName") String loggerName) {
Map<String, String> loggerInfo = LoggerUtils.getLoggerInfo(loggerName);
if (loggerInfo == null) {
throw new WebApplicationException(String.format("Logger %s not found", loggerName), Response.Status.NOT_FOUND);
}
return loggerInfo;
}

@PUT
@Path("/loggers/{loggerName}")
@Produces(MediaType.APPLICATION_JSON)
@ApiOperation(value = "Set logger level", notes = "Set logger level for a given logger")
public Map<String, String> setLoggerLevel(@ApiParam(value = "Logger name") @PathParam("loggerName") String loggerName,
@ApiParam(value = "Logger level") @QueryParam("level") String level) {
return LoggerUtils.setLoggerLevel(loggerName, level);
}
}

0 comments on commit 5395c67

Please sign in to comment.