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 * Mixin flavor of the {@code /loggers} runtime log-level management endpoint.
028 *
029 * <p>
030 * Composes the logger read/set endpoints into a host resource via
031 * {@link Rest#mixins() @Rest(mixins=LoggersMixin.class)}.  Delegates to a shared {@link LoggersManager}
032 * worker so the mixin and {@link LoggersResource resource} flavors cannot drift.
033 *
034 * <p>
035 * The {@code GET} endpoints (list all / read one) are read-only, but &mdash; unlike the {@code PUT}/{@code POST}
036 * set-level endpoint, which <b>mutates runtime logging</b> and is deny-by-default (returns {@code 403} unless a
037 * {@link LoggersSettings} bean with writes enabled is registered) &mdash; they carry <b>no built-in guard of their
038 * own</b>: once a resource composes this mixin, {@code GET /loggers} and {@code GET /loggers/{name}} are reachable
039 * by anyone who can reach the resource, exposing the set of registered logger names and their configured levels
040 * (topology information, not credentials or request data). Compose this mixin only into a resource, or a subpath
041 * of one, that's already covered by whatever authentication/authorization the app applies to its management
042 * endpoints generally; this mixin does not add any of its own for the read side.
043 *
044 * <h5 class='section'>Backend:</h5><ul>
045 *    <li>Drives {@link JulLogBackend java.util.logging} by default; declare a Logback / Log4j2 backend
046 *       explicitly via {@link LoggersSettings.Builder#backend(LogBackend)} &mdash; see {@link LogBackend}.
047 * </ul>
048 *
049 * <h5 class='section'>See Also:</h5><ul>
050 *    <li class='jc'>{@link LoggersResource}
051 *    <li class='jc'>{@link LoggersManager}
052 *    <li class='jc'>{@link LogBackend}
053 * </ul>
054 *
055 * @since 10.0.0
056 */
057@Rest
058public class LoggersMixin extends RestMixin {
059
060   private final LoggersManager manager = new LoggersManager();
061
062   /**
063    * [GET /loggers] - All loggers and their configured levels.
064    *
065    * @param req The HTTP request.
066    * @return A sorted map of logger name to configured level (empty string = inherited).
067    */
068   @RestGet(
069      path="/loggers",
070      summary="Runtime logger levels",
071      description="Lists all loggers and their configured levels (empty = inherited from ancestor)."
072   )
073   public Map<String,String> getLoggers(RestRequest req) {
074      return manager.getLevels(req.getContext());
075   }
076
077   /**
078    * [GET /loggers/{name}] - One logger's configured level.
079    *
080    * @param req The HTTP request.
081    * @param name The logger name ("ROOT" for the root logger).
082    * @return The configured level name, empty string if inherited.
083    * @throws NotFound If no logger with that name is registered.
084    */
085   @RestGet(
086      path="/loggers/{name}",
087      summary="Runtime logger level",
088      description="Returns the configured level of a single logger."
089   )
090   public String getLogger(RestRequest req, @Path("name") String name) {
091      var level = manager.getLevel(req.getContext(), name);
092      if (level == null)
093         throw new NotFound("No logger named '%s' is registered.", name);
094      return level;
095   }
096
097   /**
098    * [PUT /loggers/{name}] - Set a logger's level at runtime.
099    *
100    * <p>
101    * <b>Sensitive (mutating) &mdash; deny-by-default:</b> responds {@code 403} unless a {@link LoggersSettings}
102    * bean with writes enabled is registered.
103    *
104    * @param req The HTTP request.
105    * @param name The logger name ("ROOT" for the root logger).
106    * @param level The level name (e.g. FINE, INFO, OFF), or blank to inherit from the ancestor.
107    * @return The newly-configured level (empty string if inherited).
108    * @throws Forbidden If the set-level endpoint is not enabled.
109    */
110   @RestPut(
111      path="/loggers/{name}",
112      summary="Set logger level",
113      description="Sets the java.util.logging level of a single logger at runtime (blank to inherit).  Disabled by default; opt in via LoggersSettings."
114   )
115   public String setLogger(RestRequest req, @Path("name") String name, @Content String level) {
116      if (! manager.resolveSettings(req.getContext()).isWriteEnabled())
117         throw new Forbidden("The /loggers set-level endpoint is disabled.  Register a LoggersSettings bean with write enabled to use it.");
118      manager.setLevel(req.getContext(), name, level);
119      // setLevel creates the logger on demand, so getLevel always returns non-null here (level name or "" if inherited).
120      return manager.getLevel(req.getContext(), name);
121   }
122
123   /**
124    * [POST /loggers/{name}] - Set a logger's level at runtime (POST alias of {@link #setLogger(RestRequest,String,String)}).
125    *
126    * @param req The HTTP request.
127    * @param name The logger name ("ROOT" for the root logger).
128    * @param level The level name, or blank to inherit.
129    * @return The newly-configured level (empty string if inherited).
130    * @throws Forbidden If the set-level endpoint is not enabled.
131    */
132   @RestPost(
133      path="/loggers/{name}",
134      summary="Set logger level",
135      description="Sets the java.util.logging level of a single logger at runtime (blank to inherit).  Disabled by default; opt in via LoggersSettings."
136   )
137   public String setLoggerPost(RestRequest req, @Path("name") String name, @Content String level) {
138      return setLogger(req, name, level);
139   }
140}