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.observation;
018
019
020import java.util.concurrent.atomic.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022
023/**
024 * Static facade over an explicitly-installed default {@link Observer} for ergonomic custom
025 * (non-request) observations.
026 *
027 * <p>
028 * {@code juneau-rest-server} has no method-interception / AOP substrate, so custom observations are
029 * <b>explicit</b>: wrap the block of work in a try-with-resources over {@link #observe(String, String)}.
030 * This mirrors how the request path is instrumented (the framework opens / closes the observation around
031 * the {@code @RestOp} handler) but lets application code instrument arbitrary work.
032 *
033 * <h5 class='topic'>Installing a backend</h5>
034 *
035 * <p>
036 * The default observer is {@link Observer#NOOP} until a backend is installed via {@link #install(Observer)}
037 * &mdash; typically once at startup, after the application's {@code MetricsRecorder} / {@code TracerHook}
038 * beans are known. Until then (and in any process that never installs one) {@link #observe(String, String)}
039 * hands back {@link Observation#NOOP} and costs nothing.
040 *
041 * <p class='bjava'>
042 *    <jc>// At startup, once the observability beans are built:</jc>
043 *    Observations.<jsm>install</jsm>(<jk>new</jk> Observer(<jv>metricsRecorder</jv>, <jv>tracerHook</jv>));
044 *
045 *    <jc>// Anywhere in application code:</jc>
046 *    <jk>try</jk> (Observation <jv>o</jv> = Observations.<jsm>observe</jsm>(<js>"order.load"</js>, <js>"team=payments"</js>)) {
047 *       <jk>return</jk> loadOrder(<jv>id</jv>);
048 *    } <jk>catch</jk> (RuntimeException <jv>e</jv>) {
049 *       <jv>o</jv>.setError(<jv>e</jv>);
050 *       <jk>throw</jk> <jv>e</jv>;
051 *    }
052 * </p>
053 *
054 * <p>
055 * Code that prefers explicit dependency injection over a global can hold an {@link Observer} directly
056 * (resolved from the {@code RestContext} bean store) and call {@link Observer#start(String, String)} &mdash;
057 * this facade is a convenience, not the only entry point.
058 *
059 * <h5 class='section'>See Also:</h5><ul>
060 *    <li class='jc'>{@link Observer}
061 *    <li class='jc'>{@link Observation}
062 * </ul>
063 *
064 * @since 10.0.0
065 */
066@SuppressWarnings({
067   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
068   "resource" // observe(...) returns the Observation handle for the caller's try-with-resources; closing it here is wrong.
069})
070public class Observations {
071
072   private static final AtomicReference<Observer> defaultObserver = new AtomicReference<>(Observer.NOOP);
073
074   private Observations() {}
075
076   /**
077    * Installs the process-wide default {@link Observer} used by {@link #observe(String, String)}.
078    *
079    * <p>
080    * Intended to be called once at application startup. Passing {@link Observer#NOOP} (or calling
081    * {@link #reset()}) restores the no-op default.
082    *
083    * @param observer The observer to install. Must not be <jk>null</jk>.
084    */
085   public static void install(Observer observer) {
086      defaultObserver.set(reqnn("observer", observer));
087   }
088
089   /**
090    * Resets the process-wide default observer back to {@link Observer#NOOP}.
091    */
092   public static void reset() {
093      defaultObserver.set(Observer.NOOP);
094   }
095
096   /**
097    * Returns the currently-installed default {@link Observer}.
098    *
099    * @return The default observer; {@link Observer#NOOP} if none was installed. Never <jk>null</jk>.
100    */
101   public static Observer observer() {
102      return defaultObserver.get();
103   }
104
105   /**
106    * Starts a custom observation against the installed default {@link Observer}.
107    *
108    * @param name The observation name &mdash; used as both the span name and the metric timer name.
109    *    Must not be <jk>null</jk> or blank.
110    * @param tags Additional metric tags as comma-separated {@code key=value} pairs, or <jk>null</jk> /
111    *    empty for none.
112    * @return The started {@link Observation} (a try-with-resources handle). Never <jk>null</jk>.
113    */
114   public static Observation observe(String name, String tags) {
115      return defaultObserver.get().start(name, tags);
116   }
117
118   /**
119    * Starts a custom observation with no additional tags.
120    *
121    * @param name The observation name. Must not be <jk>null</jk> or blank.
122    * @return The started {@link Observation}. Never <jk>null</jk>.
123    */
124   public static Observation observe(String name) {
125      return defaultObserver.get().start(name, "");
126   }
127}