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 java.util.*;
020
021import org.apache.juneau.http.*;
022import org.apache.juneau.http.response.*;
023import org.apache.juneau.rest.server.*;
024import org.apache.juneau.rest.server.servlet.*;
025
026/**
027 * Child-resource flavor of the {@code /loggers} runtime log-level management endpoint.
028 *
029 * <p>
030 * Mounts as a routed child via {@link Rest#children() @Rest(children=LoggersResource.class)} under the
031 * subtree {@code /loggers} and delegates to a shared {@link LoggersManager} worker &mdash; the same logic
032 * the {@link LoggersMixin mixin} flavor uses, so the two forms cannot drift.  Extends
033 * {@link BasicRestResource} so the returned beans serialize via the {@code BasicUniversalConfig} set
034 * (mirrors {@code HealthResource}).
035 *
036 * <p>
037 * As with the mixin flavor, the {@code GET} endpoints carry no built-in guard of their own &mdash; mount this
038 * resource only under a subtree that's already covered by whatever authentication/authorization the app applies
039 * to its management endpoints generally. See {@link LoggersMixin} for details.
040 *
041 * <h5 class='section'>Backend:</h5><ul>
042 *    <li>Drives {@link JulLogBackend java.util.logging} by default; declare a Logback / Log4j2 backend
043 *       explicitly via {@link LoggersSettings.Builder#backend(LogBackend)} &mdash; see {@link LogBackend}.
044 * </ul>
045 *
046 * <h5 class='section'>See Also:</h5><ul>
047 *    <li class='jc'>{@link LoggersMixin}
048 *    <li class='jc'>{@link LoggersManager}
049 *    <li class='jc'>{@link LogBackend}
050 * </ul>
051 *
052 * @since 10.0.0
053 */
054@Rest(path="/loggers")
055public class LoggersResource extends BasicRestResource {
056
057   private final LoggersManager manager = new LoggersManager();
058
059   /**
060    * [GET /] - All loggers and their configured levels.
061    *
062    * @return A sorted map of logger name to configured level (empty string = inherited).
063    */
064   @RestGet(
065      path="/*",
066      summary="Runtime logger levels",
067      description="Lists all loggers and their configured levels (empty = inherited from ancestor)."
068   )
069   public Map<String,String> getLoggers(RestRequest req) {
070      return manager.getLevels(req.getContext());
071   }
072
073   /**
074    * [GET /{name}] - One logger's configured level.
075    *
076    * @param req The HTTP request.
077    * @param name The logger name ("ROOT" for the root logger).
078    * @return The configured level name, empty string if inherited.
079    * @throws NotFound If no logger with that name is registered.
080    */
081   @RestGet(
082      path="/{name}",
083      summary="Runtime logger level",
084      description="Returns the configured level of a single logger."
085   )
086   public String getLogger(RestRequest req, @Path("name") String name) {
087      var level = manager.getLevel(req.getContext(), name);
088      if (level == null)
089         throw new NotFound("No logger named '%s' is registered.", name);
090      return level;
091   }
092
093   /**
094    * [PUT /{name}] - Set a logger's level at runtime.
095    *
096    * <p>
097    * <b>Sensitive (mutating) &mdash; deny-by-default:</b> responds {@code 403} unless a {@link LoggersSettings}
098    * bean with writes enabled is registered.
099    *
100    * @param req The HTTP request.
101    * @param name The logger name ("ROOT" for the root logger).
102    * @param level The level name (e.g. FINE, INFO, OFF), or blank to inherit from the ancestor.
103    * @return The newly-configured level (empty string if inherited).
104    * @throws Forbidden If the set-level endpoint is not enabled.
105    */
106   @RestPut(
107      path="/{name}",
108      summary="Set logger level",
109      description="Sets the java.util.logging level of a single logger at runtime (blank to inherit).  Disabled by default; opt in via LoggersSettings."
110   )
111   public String setLogger(RestRequest req, @Path("name") String name, @Content String level) {
112      if (! manager.resolveSettings(req.getContext()).isWriteEnabled())
113         throw new Forbidden("The /loggers set-level endpoint is disabled.  Register a LoggersSettings bean with write enabled to use it.");
114      manager.setLevel(req.getContext(), name, level);
115      // setLevel creates the logger on demand, so getLevel always returns non-null here (level name or "" if inherited).
116      return manager.getLevel(req.getContext(), name);
117   }
118
119   /**
120    * [POST /{name}] - Set a logger's level at runtime (POST alias of {@link #setLogger(RestRequest,String,String)}).
121    *
122    * @param req The HTTP request.
123    * @param name The logger name ("ROOT" for the root logger).
124    * @param level The level name, or blank to inherit.
125    * @return The newly-configured level (empty string if inherited).
126    * @throws Forbidden If the set-level endpoint is not enabled.
127    */
128   @RestPost(
129      path="/{name}",
130      summary="Set logger level",
131      description="Sets the java.util.logging level of a single logger at runtime (blank to inherit).  Disabled by default; opt in via LoggersSettings."
132   )
133   public String setLoggerPost(RestRequest req, @Path("name") String name, @Content String level) {
134      return setLogger(req, name, level);
135   }
136}