-
Notifications
You must be signed in to change notification settings - Fork 410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added Http Trace Context #143
Changes from 22 commits
d0e637d
555e05c
8245e6a
0442a52
29e89de
fb29a2b
6cff40e
f614fb9
838c94e
b09a788
2e2f9df
4476a31
c88cc14
2f081d8
dd2d515
a4e1cbd
974d1bd
c1314cb
c5f1f2d
77abce8
d3a1931
ed44b0a
1462b48
5d13af0
49200ac
d2c3ebf
aacecd5
bf134b8
3647d6a
956055b
374d7c4
2873ef0
692967b
b8df0b1
26da008
98ca4e0
6826a41
1f47c39
2ded3e1
fc1ef96
94831ab
23d78d1
24ab44b
b64427e
78c4158
77ca5ca
3f0173c
f0db27e
a58b4ee
76d6f29
93e0e66
7a2d5f2
69fc0d6
818865f
6dccadf
8b84e55
3290a17
0eb2e77
1f0b6e1
e0c8d5f
e602890
1a5d792
c2952e8
1f0d5e7
acdf179
a6c150b
19a4cd5
28e4d0a
d22b43e
5429141
8c3d6fd
81a0640
513b0e0
f388ac1
94834dd
5026789
fe62212
92f0e0f
6d90293
25e549d
5a71b43
01d8cec
427030a
d8bbd52
7580adb
544855b
349fbb9
4104e91
bbdfed1
d67c502
cf25139
10463c0
c0ac15e
36b3241
6b6b533
c706b4c
053d92e
458d4aa
20f747b
5e2ee69
bc3fb98
38a94a5
fa070d4
0e0d344
e0c63d3
886d893
bcfb751
d2fc434
3ebe9e6
6d0cd8b
20259a9
11efe1f
c1adf5a
a19cd59
d392f64
d774328
99be4b7
87ae915
a78b5d1
17d8c3e
9168e67
e4d6474
47b6a6f
c78c8cb
edd9188
9e4e820
6d4a23f
d95f29e
7a9e9ba
fa90b35
c1fb682
5fe65b6
5701303
c42cd38
07c5930
b182a5f
3887dc7
1d47802
0d53d33
2de9266
cd47c3a
e7ebefa
820dd14
7dc1203
3f73d5d
3fa337a
f31f9fa
93a9389
5bf0693
544c0ee
2064606
69b688c
aee8cfd
5e054ee
d066f4e
c0e74ff
466d4f3
c5164c9
2cd73ce
bd41f80
051089d
249c32c
b6002fc
3e89067
19ca833
5d6a288
1a90fec
8aa9b7f
1c89300
f928930
2e813fb
017e470
9bb3984
3eb1d6a
a0e2f23
51a380a
d5edbec
8ac9486
af1b484
eddb689
fabc345
0a8021a
31220c6
190a7fa
fd8dd98
185e8a3
432ab0d
d0724ae
0b1a90f
f0c1c9d
8e6c3a1
eb8c38f
dd7f65d
bae2047
e8eeb9d
d86fbd3
4ff679b
3f03636
42c1ace
5f23ebe
09bd626
c7aa9ae
186f47f
987b90c
e063cb7
f423173
2925d5a
5eaa32d
ba2bfaa
1dfc9d7
6acc97f
b04a00f
c367480
e98bb2a
127c6f1
7df20dd
c6690b0
5b66308
1144903
1ff6c52
fb0ac53
43722fd
a5166a9
8f82a70
8f0d0c4
0d83afa
43ec98e
9576052
0947b16
7f560ed
298cf43
e27a159
2a0e5b3
5b5b930
431ee66
e78ad86
16c340a
7c6cba8
5a0a40f
d6fe242
24e490d
b090657
e75105e
3fb3182
ae9485c
3da1f13
d5f869e
4b9605e
b7d0eb4
9a27369
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,3 +34,8 @@ | |
|
||
# Bazel files | ||
/bazel-* | ||
/.idea/ | ||
/.idea/.gitignore | ||
/.idea/modules.xml | ||
/.idea/opentelemetry-cpp.iml | ||
/.idea/vcs.xml |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
#include "opentelemetry/trace/span.h" | ||
Tianlin-Zhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
OPENTELEMETRY_BEGIN_NAMESPACE | ||
namespace trace { | ||
public const class DefaultSpan : Span { | ||
public: | ||
// Returns an invalid span. | ||
static DefaultSpan getInvalid() { | ||
return INVALID; | ||
} | ||
|
||
// Creates an instance of this class with spancontext. | ||
static DefaultSpan create(SpanContext spanContext) { | ||
return DefaultSpan(spanContext); | ||
} | ||
|
||
static DefaultSpan createRandom() { | ||
return DefaultSpan( | ||
SpanContext( | ||
TraceId.generateRandomId(), | ||
SpanId.generateRandomId(), | ||
false, | ||
TraceFlags.getDefault(), | ||
TraceState.getDefault() | ||
) | ||
); | ||
} | ||
|
||
DefaultSpan(SpanContext spanContext) { | ||
this.spanContext = spanContext; | ||
} | ||
|
||
private: | ||
static const DefaultSpan INVALID = new DefaultSpan(SpanContext.getInvalid()); | ||
const SpanContext spanContext; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,183 @@ | ||||||||
#include <vector> | ||||||||
Tianlin-Zhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
#include <regex> | ||||||||
#include <stdexcept> | ||||||||
#include "opentelemetry/trace/propagation/httptextformat.h" | ||||||||
#include "opentelemetry/trace/spancontext.h" | ||||||||
#include "opentelemetry/trace/trace_state.h" | ||||||||
#include "opentelemetry/context/context.h" | ||||||||
#include "opentelemetry/nostd/string_view.h" | ||||||||
#include "opentelemetry/trace/span.h" | ||||||||
#include "opentelemetry/trace/default_span.cc" | ||||||||
|
||||||||
OPENTELEMETRY_BEGIN_NAMESPACE | ||||||||
namespace trace | ||||||||
{ | ||||||||
namespace propagation | ||||||||
{ | ||||||||
namespace | ||||||||
{ | ||||||||
|
||||||||
static Context SetSpanInContext(Span span, Context &context) { | ||||||||
Context new_values = Context(context); | ||||||||
// I don't know if the SPAN_KEY is defined in the context.h. | ||||||||
// My point is that since each key when it is created is unique in terms of its id even though they may have the same name, | ||||||||
// it would make sense to define those keys in a single file only and had to be referenced by other files to store and retrieve values, | ||||||||
// otherwise I will not be able to access any fields, for example, "current-span" as CreateKey("current-span") will | ||||||||
// not work because the id is different when the value is put into despite the Key is also created from | ||||||||
// CreateKey("current-span"). | ||||||||
// Don't know if I get the correct understanding there. | ||||||||
new_values.setValue(Context.SPAN_KEY,span); | ||||||||
return new_values; | ||||||||
} | ||||||||
|
||||||||
static Span GetCurrentSpan(Context &context) { | ||||||||
Span span = get_value(Context.SPAN_KEY, context); | ||||||||
if (span == NULL) { | ||||||||
return NULL; | ||||||||
} | ||||||||
return span; | ||||||||
} | ||||||||
|
||||||||
// The HttpTraceContext provides methods to extract and inject | ||||||||
// context into headers of HTTP requests with traces. | ||||||||
// Example: | ||||||||
// HttpTraceContext.inject(setter,&carrier,&context); | ||||||||
// HttpTraceContext.extract(getter,&carrier,&context); | ||||||||
class HttpTraceContext : public HTTPTextFormat | ||||||||
{ | ||||||||
public: | ||||||||
List<nostd::string_view> fields() { | ||||||||
static const auto* FIELDS = new std::vector<nostd::string_view>({TRACE_PARENT, TRACE_STATE}); | ||||||||
return FIELDS; | ||||||||
} | ||||||||
|
||||||||
void inject(Setter setter, T &carrier, const Context &context) override { | ||||||||
common::AttributeValue span = GetCurrentSpan(context); | ||||||||
if (span == NULL || !span.getContext().isValid()) { | ||||||||
// We don't have span.getContext() in span.h, should we just use span? As well as acquiring validity. (I do know how to implement them though) | ||||||||
return; | ||||||||
} | ||||||||
injectImpl(setter, carrier, span.getContext()); | ||||||||
} | ||||||||
|
||||||||
Context extract(Getter getter, const T &carrier, Context &context) override { | ||||||||
SpanContext spanContext = extractImpl(carrier, getter); | ||||||||
return SetSpanInContext(trace.DefaultSpan(spanContext), context); | ||||||||
} | ||||||||
|
||||||||
private: | ||||||||
static const nostd::string_view TRACE_PARENT = "traceparent"; | ||||||||
static const nostd::string_view TRACE_STATE = "tracestate"; | ||||||||
static const int VERSION_BYTES = 2; | ||||||||
static const int TRACE_ID_BYTES = 32; | ||||||||
static const int PARENT_ID_BYTES = 16; | ||||||||
static const int TRACE_FLAG_BYTES = 2; | ||||||||
static const int TRACE_DELIMITER_BYTES = 3; | ||||||||
static const int HEADER_SIZE = VERSION_BYTES + TRACE_ID_BYTES + PARENT_ID_BYTES + TRACE_FLAG_BYTES + TRACE_DELIMITER_BYTES; | ||||||||
static const int TRACESTATE_MAX_MEMBERS = 32; | ||||||||
static const nostd::string_view TRACESTATE_KEY_VALUE_DELIMITER = "="; | ||||||||
static const std::regex TRACESTATE_ENTRY_DELIMITER_SPLIT_PATTERN("[ \t]*,[ \t]*"); | ||||||||
|
||||||||
static Context checkNotNull(Context &arg, nostd::string_view errorMessage) { | ||||||||
if (arg == NULL) { | ||||||||
throw new NullPointerException("error: null " + errorMessage); | ||||||||
Tianlin-Zhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
} | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||
return arg; | ||||||||
} | ||||||||
|
||||||||
static void injectImpl(Setter setter, T &carrier, const SpanContext &spanContext) { | ||||||||
char hex_string[HEADER_SIZE]; | ||||||||
sprintf(hex_string, "00-%032x-%016x-%02x",spanContext.trace_id(),spanContext.span_id(),spanContext.trace_flags()); | ||||||||
Tianlin-Zhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
nostd::string_view traceparent_string = nostd::string_view(hex_string, HEADER_SIZE); | ||||||||
setter(carrier, TRACE_PARENT, traceparent_string); | ||||||||
if (spanContext.getTraceState()) { | ||||||||
nostd::string_view tracestate_string = formatTracestate(spanContext.getTraceState()); // I need the definition for the type of TraceState(Dictionary or something else). The trace state data structure will determine how I will able to join this together. | ||||||||
setter(carrier, TRACE_STATE, tracestate_string); | ||||||||
Tianlin-Zhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
} | ||||||||
} | ||||||||
|
||||||||
static SpanContext extractContextFromTraceParent(nostd::string_view &traceparent) { | ||||||||
bool isValid = traceparent.length() == HEADER_SIZE | ||||||||
&& traceparent[VERSION_BYTES] == "-" | ||||||||
&& traceparent[VERSION_BYTES+TRACE_ID_BYTES+1] == "-" | ||||||||
&& traceparent[VERSION_BYTES+TRACE_ID_BYTES+PARENT_ID_BYTES+2] == "-"; | ||||||||
if (!isValid) { | ||||||||
std::cout<<"Unparseable traceparent header. Returning INVALID span context."<<std::endl; | ||||||||
return SpanContext.getInvalid(); | ||||||||
} | ||||||||
|
||||||||
try { | ||||||||
TraceId traceId = TraceId.fromLowerBase16(traceparent, TRACE_ID_OFFSET); | ||||||||
SpanId spanId = SpanId.fromLowerBase16(traceparent, SPAN_ID_OFFSET); | ||||||||
TraceFlags traceFlags = TraceFlags.fromLowerBase16(traceparent, TRACE_OPTION_OFFSET); | ||||||||
return SpanContext.createFromRemoteParent(traceId, spanId, traceFlags, TRACE_STATE_DEFAULT); | ||||||||
} catch (IllegalArgumentException e) { | ||||||||
Tianlin-Zhao marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||
std::cout<<"Unparseable traceparent header. Returning INVALID span context."<<std::endl; | ||||||||
return SpanContext.getInvalid(); | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
static TraceState extractTraceState(nostd::string_view traceStateHeader) { | ||||||||
// TODO: implementation | ||||||||
/** The question here is that how should I treat the trace state. Implementation will differ pretty much | ||||||||
depending on what is the data structure of TraceState. Also this is based on the premise that | ||||||||
trace state exists and has a builder | ||||||||
*/ | ||||||||
std::smatch listMembers; | ||||||||
TraceState.Builder traceStateBuilder = TraceState.builder(); | ||||||||
regex_search(traceStateHeader,sm,TRACESTATE_ENTRY_DELIMITER_SPLIT_PATTERN); // I hope regex accepts string view | ||||||||
if (listMembers.size() <= TRACESTATE_MAX_MEMBERS) { | ||||||||
throw std::invalid_argument("TraceState has too many elements."); | ||||||||
} | ||||||||
for (int i = listMembers.size() - 1; i >= 0; i--) { | ||||||||
nostd::string_view listMember = listMembers[i]; | ||||||||
int index = -1; | ||||||||
for (int j = 0; j < listMember.length(); j++) { | ||||||||
if (listMember[j] == TRACESTATE_KEY_VALUE_DELIMITER) { | ||||||||
index = j; | ||||||||
break; | ||||||||
} | ||||||||
} | ||||||||
if (index == -1) { | ||||||||
throw std::invalid_argument("Invalid TraceState list-member format."); | ||||||||
} | ||||||||
traceStateBuilder.set(listMember.substring(0, index), listMember.substring(index + 1)); | ||||||||
} | ||||||||
return traceStateBuilder.build(); | ||||||||
} | ||||||||
|
||||||||
static SpanContext extractImpl(Getter getter, T &carrier) { | ||||||||
nostd::string_view traceParent = getter(carrier, TRACE_PARENT); | ||||||||
if (traceParent == NULL) { | ||||||||
return SpanContext.getInvalid(); | ||||||||
} | ||||||||
|
||||||||
SpanContext contextFromParentHeader = extractContextFromTraceParent(traceParent); | ||||||||
if (!contextFromParentHeader.isValid()) { | ||||||||
return contextFromParentHeader; | ||||||||
} | ||||||||
|
||||||||
nostd::string_view traceStateHeader = getter(carrier, TRACE_STATE); | ||||||||
if (traceStateHeader == NULL || traceStateHeader.isEmpty()) { | ||||||||
return contextFromParentHeader; | ||||||||
} | ||||||||
|
||||||||
try { | ||||||||
// Again, the trace state problem, should I treat it like a string or dictionary or an encapsuled class? This is not defined in current files | ||||||||
TraceState traceState = extractTraceState(traceStateHeader); | ||||||||
// Need getter support from SpanContext | ||||||||
return SpanContext.createFromRemoteParent( | ||||||||
contextFromParentHeader.getTraceId(), | ||||||||
contextFromParentHeader.getSpanId(), | ||||||||
contextFromParentHeader.getTraceFlags(), | ||||||||
traceState); | ||||||||
} catch (IllegalArgumentException e) { | ||||||||
std::cout<<"Unparseable tracestate header. Returning span context without state."<<std::endl; | ||||||||
return contextFromParentHeader; | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
} // namespace trace | ||||||||
OPENTELEMETRY_END_NAMESPACE |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
#pragma once | ||
|
||
#include <cstdint> | ||
#include "opentelemetry/context/context.h" | ||
#include "opentelemetry/nostd/string_view.h" | ||
#include "opentelemetry/version.h" | ||
|
||
OPENTELEMETRY_BEGIN_NAMESPACE | ||
namespace trace | ||
{ | ||
namespace propagation | ||
{ | ||
|
||
// Set the span in the given context. | ||
virtual static Context SetSpanInContext(Span span, Context &context) = 0; | ||
// Retrieve the current span. | ||
virtual static Span GetCurrentSpan(Context &context) = 0; | ||
|
||
// The HTTPTextFormat class provides an interface that enables extracting and injecting | ||
// context into headers of HTTP requests. HTTP frameworks and clients | ||
// can integrate with HTTPTextFormat by providing the object containing the | ||
// headers, and a getter and setter function for the extraction and | ||
// injection of values, respectively. | ||
template <typename T> | ||
class HTTPTextFormat { | ||
public: | ||
// Rules that manages how context will be extracted from carrier. | ||
using Getter = nostd::string_view(*)(T &carrier, nostd::string_view trace_type); | ||
|
||
// Rules that manages how context will be injected to carrier. | ||
using Setter = void(*)(T &carrier, nostd::string_view trace_type,nostd::string trace_description); | ||
|
||
// Returns the context that is stored in the HTTP header carrier with self defined rules. | ||
virtual Context extract(Getter get_from_carrier, const T &carrier, Context &context) = 0; | ||
|
||
// Sets the context for a HTTP header carrier with self defined rules. | ||
virtual void inject(Setter set_from_carrier, T &carrier, const Context &context) = 0; | ||
}; | ||
} | ||
} | ||
OPENTELEMETRY_END_NAMESPACE |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -47,6 +47,14 @@ class SpanId final | |
} | ||
} | ||
|
||
// Creates a SpanId from traceparent | ||
static SpanId fromLowerBase16(nostd::string_view src, int srcOffset) | ||
{ | ||
// I don't really know about what to do with this function | ||
// The java implementation has this: return new SpanId(BigendianEncoding.longFromBase16String(src, srcOffset)); | ||
// But I am not sure if this will suffice here and I don't know what is BigendianEncoding module as well. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A SpanId is represented internally with a I suggest you define a helper function to map from nostd::string_view to uint8_t[] that can be used here and in the TraceId case. That function can then be used when calling the SpanId constructor that takes a nostd::span of uint8_t (and then this function doesn't need to exist). I also suggest that you have the caller handle the offset by using string_view substringing |
||
} | ||
|
||
// Returns a nostd::span of the ID. | ||
nostd::span<const uint8_t, kSize> Id() const noexcept | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
#pragma once | ||
|
||
#include <cstdint> | ||
#include "opentelemetry/context/context.h" | ||
#include "opentelemetry/nostd/string_view.h" | ||
#include "opentelemetry/version.h" | ||
#include "opentelemetry/trace/trace_id.h" | ||
#include "opentelemetry/trace/span_id.h" | ||
#include "opentelemetry/trace/trace_flags.h" | ||
#include "opentelemetry/trace/trace_state.h" | ||
|
||
OPENTELEMETRY_BEGIN_NAMESPACE | ||
namespace trace | ||
{ | ||
|
||
public class SpanContext: | ||
// The state of a Span to propagate between processes. | ||
// This class includes the immutable attributes of a :class:`.Span` that must | ||
// be propagated to a span's children and across process boundaries. | ||
public: | ||
TraceId traceId; | ||
SpanId spanId; | ||
bool remote; | ||
TraceFlags traceFlags; | ||
TraceState traceState; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I expect the member variables should be private, since there are getters and they're set in the constructor. |
||
static SpanContext INVALID = SpanContext(TraceId.getInvalid(),SpanId.getInvalid(),false,TraceFlags.getDefault(),TraceState.getDefault()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suggest putting INVALID inside GetInvalid() as see https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables |
||
|
||
SpanContext(TraceId trace_id, SpanId span_id, bool is_remote, TraceFlags &trace_flags, TraceState &trace_state) { | ||
traceId = trace_id; | ||
spanId = span_id; | ||
traceFlags = trace_flags; | ||
traceState = trace_state; | ||
remote = is_remote; | ||
} | ||
|
||
// Creates a new SpanContext that was propagated from a remote parent, with the given | ||
// identifiers and options. | ||
public static SpanContext createFromRemoteParent(TraceId traceId, SpanId spanId, TraceFlags traceFlags, TraceState traceState) { | ||
// Question: what is AutoValue_spanContext, where is it defined? | ||
return new AutoValue_SpanContext(traceId, spanId, traceFlags, traceState, /* remote=*/ true); | ||
} | ||
|
||
static SpanContext getInvalid() { | ||
return INVALID; | ||
} | ||
|
||
bool isValid() { | ||
// Get whether this `SpanContext` is valid. | ||
// A `SpanContext` is said to be invalid if its trace ID or span ID is | ||
// invalid (i.e. ``0``). | ||
return traceId != INVALID_TRACE_ID && self.span_id != INVALID_SPAN_ID; | ||
} | ||
|
||
TraceId getTraceId() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just FYI, the getters can be named like trace_id(), span_id(), ... |
||
return traceId; | ||
} | ||
|
||
SpanId getSpanId() { | ||
return spanId; | ||
} | ||
|
||
TraceFlags getTraceFlags() { | ||
return traceFlags; | ||
} | ||
|
||
TraceState getTraceState() { | ||
return traceState; | ||
} | ||
} | ||
OPENTELEMETRY_END_NAMESPACE |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -46,6 +46,14 @@ class TraceFlags final | |
buffer[1] = kHex[(rep_ >> 0) & 0xF]; | ||
} | ||
|
||
// Creates TraceFlags from traceparent | ||
static TraceFlags fromLowerBase16(nostd::string_view src, int srcOffset) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similar to SpanId.fromLowerBase16, use the |
||
{ | ||
// I don't really know about what to do with this function | ||
// The java implementation has this: return new TraceFlags(BigendianEncoding.byteFromBase16String(src, srcOffset)); | ||
// But I am not sure if this will suffice here and I don't know what is BigendianEncoding module as well. | ||
} | ||
|
||
uint8_t flags() const noexcept { return rep_; } | ||
|
||
bool operator==(const TraceFlags &that) const noexcept { return rep_ == that.rep_; } | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be IDE specific files, would you put them in a separate section (and put a comment)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'll do that right away. Actually, do you mind to see the other PR I've made sometime before?
It contains the header I'd like to merge into while this PR is more about consulting about some of the coding details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are the lines below needed? Isn't the whole .idea subdir ignored?
Also I'm not sure this belongs in the .gitignore file at all. I'd like to avoid e.g. every single IDE/editor being covered here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They should have been removed by now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you still plan to keep this line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've commited those changes to remove it.