aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-process/src/main/java/org/sonar/process/logging/LogLevelConfig.java
blob: ee760ffa4e5c333b364b9b8b7c7934696fa9fab5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
 * SonarQube
 * Copyright (C) 2009-2025 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.process.logging;

import ch.qos.logback.classic.Level;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Stream;
import org.sonar.process.ProcessId;

import static java.util.Objects.requireNonNull;
import static org.sonar.process.ProcessProperties.Property.LOG_LEVEL;

public final class LogLevelConfig {
  private static final String SONAR_LOG_LEVEL_PROPERTY = LOG_LEVEL.getKey();
  private static final String PROCESS_NAME_PLACEHOLDER = "XXXX";
  private static final String SONAR_PROCESS_LOG_LEVEL_PROPERTY = SONAR_LOG_LEVEL_PROPERTY + "." + PROCESS_NAME_PLACEHOLDER;

  private final Map<String, List<String>> configuredByProperties;
  private final Map<String, Level> configuredByHardcodedLevel;
  private final Set<String> offUnlessTrace;
  private final String rootLoggerName;

  private LogLevelConfig(Builder builder) {
    this.configuredByProperties = Collections.unmodifiableMap(builder.configuredByProperties);
    this.configuredByHardcodedLevel = Collections.unmodifiableMap(builder.configuredByHardcodedLevel);
    this.offUnlessTrace = Collections.unmodifiableSet(builder.offUnlessTrace);
    this.rootLoggerName = builder.rootLoggerName;
  }

  Map<String, List<String>> getConfiguredByProperties() {
    return configuredByProperties;
  }

  Map<String, Level> getConfiguredByHardcodedLevel() {
    return configuredByHardcodedLevel;
  }

  Set<String> getOffUnlessTrace() {
    return offUnlessTrace;
  }

  String getRootLoggerName() {
    return rootLoggerName;
  }

  public static Builder newBuilder(String rootLoggerName) {
    return new Builder(rootLoggerName);
  }

  public static final class Builder {
    private final Map<String, List<String>> configuredByProperties = new HashMap<>();
    private final Map<String, Level> configuredByHardcodedLevel = new HashMap<>();
    private final Set<String> offUnlessTrace = new HashSet<>();
    private final String rootLoggerName;

    private Builder(String rootLoggerName) {
      this.rootLoggerName = requireNonNull(rootLoggerName, "rootLoggerName can't be null");
    }

    /**
     * Configure the log level of the root logger to be read from the value of properties {@link #SONAR_LOG_LEVEL_PROPERTY} and
     * {@link #SONAR_PROCESS_LOG_LEVEL_PROPERTY}.
     */
    public Builder rootLevelFor(ProcessId processId) {
      checkProcessId(processId);

      levelByProperty(rootLoggerName, SONAR_LOG_LEVEL_PROPERTY, SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey()));
      return this;
    }

    /**
     * Configure the log level of the logger with the specified name to be read from the value of properties
     * {@code sonar.log.level}, {@code sonar.log.level.[process_name]} and {@code sonar.log.level.[process_name].[LogDomain#getKey()]}.
     */
    public Builder levelByDomain(String loggerName, ProcessId processId, LogDomain domain) {
      checkLoggerName(loggerName);
      checkProcessId(processId);
      requireNonNull(domain, "LogDomain can't be null");
      String processProperty = SONAR_PROCESS_LOG_LEVEL_PROPERTY.replace(PROCESS_NAME_PLACEHOLDER, processId.getKey());
      levelByProperty(loggerName, SONAR_LOG_LEVEL_PROPERTY, processProperty, processProperty + "." + domain.getKey());
      return this;
    }

    private void levelByProperty(String loggerName, String property, String... otherProperties) {
      ensureUniqueConfiguration(loggerName);
      configuredByProperties.put(loggerName, Stream.concat(Stream.of(property), Arrays.stream(otherProperties)).toList());
    }

    /**
     * Configure the log level of the logger with the specified name to be the specified one and it should never be
     * changed.
     */
    public Builder immutableLevel(String loggerName, Level level) {
      checkLoggerName(loggerName);
      requireNonNull(level, "level can't be null");
      ensureUniqueConfiguration(loggerName);
      configuredByHardcodedLevel.put(loggerName, level);
      return this;
    }

    private void ensureUniqueConfiguration(String loggerName) {
      if (configuredByProperties.containsKey(loggerName)) {
        throw new IllegalStateException("Configuration by property already registered for " + loggerName);
      }
      if (configuredByHardcodedLevel.containsKey(loggerName)) {
        throw new IllegalStateException("Configuration hardcoded level already registered for " + loggerName);
      }
      if (offUnlessTrace.contains(loggerName)) {
        throw new IllegalStateException("Configuration off unless TRACE already registered for " + loggerName);
      }
    }

    private static void checkProcessId(ProcessId processId) {
      requireNonNull(processId, "ProcessId can't be null");
    }

    private static void checkLoggerName(String loggerName) {
      requireNonNull(loggerName, "loggerName can't be null");
      if (loggerName.isEmpty()) {
        throw new IllegalArgumentException("loggerName can't be empty");
      }
    }

    public Builder offUnlessTrace(String loggerName) {
      checkLoggerName(loggerName);
      ensureUniqueConfiguration(loggerName);
      offUnlessTrace.add(loggerName);
      return this;
    }

    public LogLevelConfig build() {
      return new LogLevelConfig(this);
    }
  }
}