Skip to content

Commit 5cab647

Browse files
committed
initial agent work
1 parent e343ab9 commit 5cab647

16 files changed

Lines changed: 1314 additions & 4 deletions

bugsnag/src/main/java/com/bugsnag/Bugsnag.java

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import java.io.Closeable;
1212
import java.lang.Thread.UncaughtExceptionHandler;
1313
import java.net.Proxy;
14+
import java.util.Collection;
1415
import java.util.Collections;
1516
import java.util.Date;
1617
import java.util.Set;
@@ -57,6 +58,7 @@ public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
5758

5859
private Configuration config;
5960
private final SessionTracker sessionTracker;
61+
private final FeatureFlagStore featureFlagStore;
6062

6163
private static final ThreadLocal<Metadata> THREAD_METADATA = new ThreadLocal<Metadata>() {
6264
@Override
@@ -91,6 +93,7 @@ public Bugsnag(String apiKey, boolean sendUncaughtExceptions) {
9193

9294
config = new Configuration(apiKey);
9395
sessionTracker = new SessionTracker(config);
96+
featureFlagStore = config.copyFeatureFlagStore();
9497

9598
// Automatically send unhandled exceptions to Bugsnag using this Bugsnag
9699
config.setSendUncaughtExceptions(sendUncaughtExceptions);
@@ -348,7 +351,9 @@ public void setTimeout(int timeout) {
348351
* @see #notify(com.bugsnag.Report)
349352
*/
350353
public Report buildReport(Throwable throwable) {
351-
return new Report(config, throwable);
354+
HandledState handledState = HandledState.newInstance(
355+
HandledState.SeverityReasonType.REASON_HANDLED_EXCEPTION);
356+
return new Report(config, throwable, handledState, Thread.currentThread(), featureFlagStore);
352357
}
353358

354359
/**
@@ -404,7 +409,7 @@ public boolean notify(Throwable throwable, Severity severity, Callback callback)
404409

405410
HandledState handledState = HandledState.newInstance(
406411
HandledState.SeverityReasonType.REASON_USER_SPECIFIED, severity);
407-
Report report = new Report(config, throwable, handledState, Thread.currentThread());
412+
Report report = new Report(config, throwable, handledState, Thread.currentThread(), featureFlagStore);
408413
return notify(report, callback);
409414
}
410415

@@ -422,7 +427,7 @@ public boolean notify(Report report) {
422427
}
423428

424429
boolean notify(Throwable throwable, HandledState handledState, Thread currentThread) {
425-
Report report = new Report(config, throwable, handledState, currentThread);
430+
Report report = new Report(config, throwable, handledState, currentThread, featureFlagStore);
426431
return notify(report, null);
427432
}
428433

@@ -679,4 +684,59 @@ public static Set<Bugsnag> uncaughtExceptionClients() {
679684
void addOnSession(OnSession onSession) {
680685
sessionTracker.addOnSession(onSession);
681686
}
687+
688+
/**
689+
* Add a feature flag with the specified name and variant.
690+
* If the name already exists, the variant will be updated.
691+
*
692+
* @param name the feature flag name
693+
* @param variant the feature flag variant (can be null)
694+
*/
695+
public void addFeatureFlag(String name, String variant) {
696+
featureFlagStore.addFeatureFlag(name, variant);
697+
}
698+
699+
/**
700+
* Add a feature flag with the specified name and no variant.
701+
*
702+
* @param name the feature flag name
703+
*/
704+
public void addFeatureFlag(String name) {
705+
addFeatureFlag(name, null);
706+
}
707+
708+
/**
709+
* Add multiple feature flags.
710+
* If any names already exist, their variants will be updated.
711+
*
712+
* @param featureFlags the feature flags to add
713+
*/
714+
public void addFeatureFlags(Collection<FeatureFlag> featureFlags) {
715+
featureFlagStore.addFeatureFlags(featureFlags);
716+
}
717+
718+
/**
719+
* Remove the feature flag with the specified name.
720+
*
721+
* @param name the feature flag name to remove
722+
*/
723+
public void clearFeatureFlag(String name) {
724+
featureFlagStore.clearFeatureFlag(name);
725+
}
726+
727+
/**
728+
* Remove all feature flags.
729+
*/
730+
public void clearFeatureFlags() {
731+
featureFlagStore.clearFeatureFlags();
732+
}
733+
734+
/**
735+
* Get a copy of the feature flag store.
736+
*
737+
* @return a copy of the feature flag store
738+
*/
739+
FeatureFlagStore copyFeatureFlagStore() {
740+
return featureFlagStore.copy();
741+
}
682742
}

bugsnag/src/main/java/com/bugsnag/Configuration.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class Configuration {
4848
Collection<Callback> callbacks = new ConcurrentLinkedQueue<Callback>();
4949
private final AtomicBoolean autoCaptureSessions = new AtomicBoolean(true);
5050
private final AtomicBoolean sendUncaughtExceptions = new AtomicBoolean(true);
51+
private final FeatureFlagStore featureFlagStore = new FeatureFlagStore();
5152

5253
Configuration(String apiKey) {
5354
this.apiKey = apiKey;
@@ -299,4 +300,59 @@ public Serializer getSerializer() {
299300
public void setSerializer(Serializer serializer) {
300301
this.serializer = serializer;
301302
}
303+
304+
/**
305+
* Add a feature flag with the specified name and variant.
306+
* If the name already exists, the variant will be updated.
307+
*
308+
* @param name the feature flag name
309+
* @param variant the feature flag variant (can be null)
310+
*/
311+
public void addFeatureFlag(String name, String variant) {
312+
featureFlagStore.addFeatureFlag(name, variant);
313+
}
314+
315+
/**
316+
* Add a feature flag with the specified name and no variant.
317+
*
318+
* @param name the feature flag name
319+
*/
320+
public void addFeatureFlag(String name) {
321+
addFeatureFlag(name, null);
322+
}
323+
324+
/**
325+
* Add multiple feature flags.
326+
* If any names already exist, their variants will be updated.
327+
*
328+
* @param featureFlags the feature flags to add
329+
*/
330+
public void addFeatureFlags(Collection<FeatureFlag> featureFlags) {
331+
featureFlagStore.addFeatureFlags(featureFlags);
332+
}
333+
334+
/**
335+
* Remove the feature flag with the specified name.
336+
*
337+
* @param name the feature flag name to remove
338+
*/
339+
public void clearFeatureFlag(String name) {
340+
featureFlagStore.clearFeatureFlag(name);
341+
}
342+
343+
/**
344+
* Remove all feature flags.
345+
*/
346+
public void clearFeatureFlags() {
347+
featureFlagStore.clearFeatureFlags();
348+
}
349+
350+
/**
351+
* Get a copy of the feature flag store.
352+
*
353+
* @return a copy of the feature flag store
354+
*/
355+
FeatureFlagStore copyFeatureFlagStore() {
356+
return featureFlagStore.copy();
357+
}
302358
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package com.bugsnag;
2+
3+
import com.bugsnag.serialization.Expose;
4+
5+
import java.util.Objects;
6+
7+
/**
8+
* Represents a feature flag with a name and optional variant.
9+
* Feature flags can be used to annotate events with information about
10+
* active experiments or A/B tests.
11+
*/
12+
public class FeatureFlag {
13+
private final String name;
14+
private final String variant;
15+
16+
/**
17+
* Create a feature flag with a name and no variant.
18+
*
19+
* @param name the name of the feature flag
20+
*/
21+
public FeatureFlag(String name) {
22+
this(name, null);
23+
}
24+
25+
/**
26+
* Create a feature flag with a name and variant.
27+
*
28+
* @param name the name of the feature flag
29+
* @param variant the variant of the feature flag (can be null)
30+
*/
31+
public FeatureFlag(String name, String variant) {
32+
if (name == null || name.isEmpty()) {
33+
throw new IllegalArgumentException("Feature flag name cannot be null or empty");
34+
}
35+
this.name = name;
36+
this.variant = variant;
37+
}
38+
39+
/**
40+
* Get the name of the feature flag.
41+
*
42+
* @return the feature flag name
43+
*/
44+
@Expose
45+
public String getName() {
46+
return name;
47+
}
48+
49+
/**
50+
* Get the variant of the feature flag.
51+
*
52+
* @return the feature flag variant, or null if not set
53+
*/
54+
@Expose
55+
public String getVariant() {
56+
return variant;
57+
}
58+
59+
@Override
60+
public boolean equals(Object obj) {
61+
if (this == obj) {
62+
return true;
63+
}
64+
if (obj == null || getClass() != obj.getClass()) {
65+
return false;
66+
}
67+
FeatureFlag that = (FeatureFlag) obj;
68+
return Objects.equals(name, that.name) && Objects.equals(variant, that.variant);
69+
}
70+
71+
@Override
72+
public int hashCode() {
73+
return Objects.hash(name, variant);
74+
}
75+
76+
@Override
77+
public String toString() {
78+
return "FeatureFlag{name='" + name + "', variant='" + variant + "'}";
79+
}
80+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.bugsnag;
2+
3+
import java.util.ArrayList;
4+
import java.util.Collection;
5+
import java.util.LinkedHashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
/**
10+
* Internal storage for feature flags that maintains insertion order.
11+
* This class is thread-safe for concurrent access.
12+
*/
13+
class FeatureFlagStore {
14+
// LinkedHashMap maintains insertion order
15+
private final Map<String, String> flags = new LinkedHashMap<String, String>();
16+
17+
/**
18+
* Add a feature flag with the specified name and variant.
19+
* If the name already exists, the variant will be updated without changing position.
20+
*
21+
* @param name the feature flag name
22+
* @param variant the feature flag variant (can be null)
23+
*/
24+
synchronized void addFeatureFlag(String name, String variant) {
25+
if (name == null || name.isEmpty()) {
26+
return;
27+
}
28+
flags.put(name, variant);
29+
}
30+
31+
/**
32+
* Add multiple feature flags.
33+
* If any names already exist, their variants will be updated without changing position.
34+
*
35+
* @param featureFlags the feature flags to add
36+
*/
37+
synchronized void addFeatureFlags(Collection<FeatureFlag> featureFlags) {
38+
if (featureFlags == null) {
39+
return;
40+
}
41+
for (FeatureFlag flag : featureFlags) {
42+
if (flag != null) {
43+
addFeatureFlag(flag.getName(), flag.getVariant());
44+
}
45+
}
46+
}
47+
48+
/**
49+
* Remove the feature flag with the specified name.
50+
*
51+
* @param name the feature flag name to remove
52+
*/
53+
synchronized void clearFeatureFlag(String name) {
54+
if (name != null) {
55+
flags.remove(name);
56+
}
57+
}
58+
59+
/**
60+
* Remove all feature flags.
61+
*/
62+
synchronized void clearFeatureFlags() {
63+
flags.clear();
64+
}
65+
66+
/**
67+
* Get a list of all feature flags in insertion order.
68+
*
69+
* @return an unmodifiable list of feature flags
70+
*/
71+
synchronized List<FeatureFlag> toList() {
72+
List<FeatureFlag> result = new ArrayList<FeatureFlag>(flags.size());
73+
for (Map.Entry<String, String> entry : flags.entrySet()) {
74+
result.add(new FeatureFlag(entry.getKey(), entry.getValue()));
75+
}
76+
return result;
77+
}
78+
79+
/**
80+
* Create a copy of this store with all the same flags.
81+
*
82+
* @return a new FeatureFlagStore with the same flags
83+
*/
84+
synchronized FeatureFlagStore copy() {
85+
FeatureFlagStore copy = new FeatureFlagStore();
86+
copy.flags.putAll(this.flags);
87+
return copy;
88+
}
89+
90+
/**
91+
* Merge flags from another store into this one.
92+
* Flags from the other store will overwrite existing flags with the same name,
93+
* but will not change the position of existing flags.
94+
*
95+
* @param other the other store to merge from
96+
*/
97+
synchronized void merge(FeatureFlagStore other) {
98+
if (other == null) {
99+
return;
100+
}
101+
synchronized (other) {
102+
for (Map.Entry<String, String> entry : other.flags.entrySet()) {
103+
flags.put(entry.getKey(), entry.getValue());
104+
}
105+
}
106+
}
107+
108+
/**
109+
* Get the number of feature flags.
110+
*
111+
* @return the number of feature flags
112+
*/
113+
synchronized int size() {
114+
return flags.size();
115+
}
116+
}

0 commit comments

Comments
 (0)