001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.server.management; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022import java.util.concurrent.*; 023import java.util.logging.*; 024 025/** 026 * {@link LogBackend} implementation for {@link java.util.logging java.util.logging} (JUL). 027 * 028 * <p> 029 * This is the default backend driven by {@link LoggersManager} when no {@link LoggersSettings} declares another 030 * one, and it carries the original v1 {@code /loggers} behavior so the JUL path is unchanged: reads report each 031 * logger's own ({@link Logger#getLevel() configured}) level (empty string when inherited), the root logger is 032 * keyed {@code "ROOT"}, and sets are process-lifetime-only via {@link Logger#setLevel(Level)}. 033 * 034 * @since 10.0.0 035 */ 036@SuppressWarnings({ 037 "java:S6548" // Singleton is appropriate for a JUL logging backend — single shared instance per JVM. 038}) 039public class JulLogBackend implements LogBackend { 040 041 /** Process-wide shared instance. */ 042 public static final JulLogBackend INSTANCE = new JulLogBackend(); 043 044 // Strong references to loggers whose levels we've explicitly set. The JUL LogManager holds only weak 045 // references to loggers, so a logger with no other strong referent can be garbage-collected between a 046 // set and a subsequent read — silently dropping the runtime-configured level (the endpoint's set is 047 // documented as process-lifetime-only) and causing reads to 404. Pinning here keeps set levels effective. 048 private final Set<Logger> pinned = ConcurrentHashMap.newKeySet(); 049 050 private JulLogBackend() {} 051 052 @Override /* LogBackend */ 053 public Map<String,String> getLevels() { 054 var out = new TreeMap<String,String>(); 055 var lm = LogManager.getLogManager(); 056 var names = Collections.list(lm.getLoggerNames()); 057 for (var name : names) { 058 var logger = lm.getLogger(name); 059 if (logger == null) 060 continue; 061 var level = logger.getLevel(); 062 var key = name.isEmpty() ? "ROOT" : name; 063 out.put(key, level == null ? "" : level.getName()); 064 } 065 return out; 066 } 067 068 @Override /* LogBackend */ 069 public String getLevel(String name) { 070 var logger = LogManager.getLogManager().getLogger(resolveName(name)); 071 if (logger == null) 072 return null; 073 var level = logger.getLevel(); 074 return level == null ? "" : level.getName(); 075 } 076 077 @Override /* LogBackend */ 078 public void setLevel(String name, String level) { 079 var logger = Logger.getLogger(resolveName(name)); 080 logger.setLevel(level == null || level.isBlank() ? null : Level.parse(level.trim())); 081 pinned.add(logger); 082 } 083 084 private static String resolveName(String name) { 085 // The root logger is the empty-string-named logger; expose it under the friendlier "ROOT" alias. 086 return (name == null || eq(name, "ROOT")) ? "" : name; 087 } 088}