Skip to content

Commit b4c7f88

Browse files
Add EFCore to example (#144)
- Add EFCore endpoint to assert on OpenTelemetry.Instrumentation.EntityFrameworkCore in OATS. - Improve some formatting.
1 parent f396d4e commit b4c7f88

File tree

5 files changed

+199
-4
lines changed

5 files changed

+199
-4
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,9 @@ ServiceFabricBackup/
263263
*.ldf
264264
*.ndf
265265

266+
# SQLLite files
267+
*.db
268+
266269
# Business Intelligence projects
267270
*.rdl.data
268271
*.bim.layout

docker/docker-compose-aspnetcore/oats.yaml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ input:
66
- path: /api/MsSql/ServerInfo
77
- path: /api/MsSql/Tables
88
- path: /api/Redis/LeftPush
9+
- path: /api/todo/items
910
expected:
1011
logs:
11-
- logql: '{service_name="aspnetcore"} |~ `Application started.`'
12+
- logql: '{ service_name="aspnetcore" } |~ `Application started.`'
1213
contains:
1314
- 'Application started'
1415
metrics:
@@ -49,15 +50,15 @@ expected:
4950
error.type: '500'
5051
http.request.method: GET
5152
http.response.status_code: '500'
52-
- traceql: '{ span.http.route =~ "api/MsSql/ServerInfo"}'
53+
- traceql: '{ span.http.route =~ "api/MsSql/ServerInfo" }'
5354
spans:
5455
- name: 'master'
5556
attributes:
5657
db.system: mssql
5758
db.name: master
5859
db.statement: sp_server_info
5960
otel.library.name: OpenTelemetry.Instrumentation.SqlClient
60-
- traceql: '{ span.http.route =~ "api/MsSql/Tables"}'
61+
- traceql: '{ span.http.route =~ "api/MsSql/Tables" }'
6162
spans:
6263
- name: 'master'
6364
attributes:
@@ -71,3 +72,11 @@ expected:
7172
db.statement: LPUSH
7273
db.system: redis
7374
net.peer.name: redis
75+
- traceql: '{ span.http.route =~ "/api/todo/items/" }'
76+
spans:
77+
- name: 'main'
78+
attributes:
79+
db.system: sqlite
80+
db.name: main
81+
peer.service: /app/App_Data/TodoApp.db
82+
otel.library.name: OpenTelemetry.Instrumentation.EntityFrameworkCore

examples/net8.0/aspnetcore/Program.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// SPDX-License-Identifier: Apache-2.0
44
//
55

6+
using aspnetcore;
67
using Grafana.OpenTelemetry;
78
using Microsoft.Data.SqlClient;
89
using OpenTelemetry.Logs;
@@ -31,6 +32,7 @@
3132
builder.Services.AddHttpClient();
3233

3334
builder.Services.AddControllers();
35+
builder.Services.AddTodoApp();
3436

3537
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
3638
builder.Services.AddEndpointsApiExplorer();
@@ -43,6 +45,7 @@
4345

4446
app.UseAuthorization();
4547
app.MapControllers();
48+
app.MapTodoApp();
4649

4750
app.MapGet("/", () => Results.Redirect("/swagger"));
4851

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
//
2+
// Copyright Grafana Labs
3+
// SPDX-License-Identifier: Apache-2.0
4+
//
5+
6+
using Microsoft.EntityFrameworkCore;
7+
8+
namespace aspnetcore;
9+
10+
public static class TodoAppEndpoints
11+
{
12+
public static IServiceCollection AddTodoApp(this IServiceCollection services)
13+
{
14+
services.AddSingleton(TimeProvider.System);
15+
services.AddScoped<TodoRepository>();
16+
17+
services.AddDbContext<TodoContext>((serviceProvider, options) =>
18+
{
19+
var configuration = serviceProvider.GetRequiredService<IConfiguration>();
20+
var dataDirectory = configuration["DataDirectory"];
21+
22+
if (string.IsNullOrEmpty(dataDirectory) || !Path.IsPathRooted(dataDirectory))
23+
{
24+
var environment = serviceProvider.GetRequiredService<IHostEnvironment>();
25+
dataDirectory = Path.Combine(environment.ContentRootPath, "App_Data");
26+
}
27+
28+
if (!Directory.Exists(dataDirectory))
29+
{
30+
Directory.CreateDirectory(dataDirectory);
31+
}
32+
33+
var databaseFile = Path.Combine(dataDirectory, "TodoApp.db");
34+
35+
options.UseSqlite("Data Source=" + databaseFile);
36+
});
37+
38+
return services;
39+
}
40+
41+
public static IEndpointRouteBuilder MapTodoApp(this IEndpointRouteBuilder builder)
42+
{
43+
var todos = builder.MapGroup("/api/todo/items").WithTags("Todo");
44+
{
45+
todos.MapPost("/", async (CreateTodoItemModel model, TodoRepository repository) =>
46+
{
47+
var id = await repository.AddItemAsync(model.Text);
48+
return Results.Created($"/api/items/{id}", new { Id = id });
49+
});
50+
51+
todos.MapGet("/", async (TodoRepository repository) => await repository.GetItemsAsync());
52+
53+
todos.MapGet("/{id}", async (Guid id, TodoRepository repository) => await repository.GetItemAsync(id));
54+
55+
todos.MapPost("/{id}/complete", async (Guid id, TodoRepository repository) =>
56+
{
57+
return await repository.CompleteItemAsync(id) switch
58+
{
59+
true => Results.NoContent(),
60+
false => Results.Problem(statusCode: StatusCodes.Status400BadRequest),
61+
_ => Results.Problem(statusCode: StatusCodes.Status404NotFound),
62+
};
63+
});
64+
65+
todos.MapDelete("/{id}", async (Guid id, TodoRepository repository) =>
66+
{
67+
var deleted = await repository.DeleteItemAsync(id);
68+
return deleted switch
69+
{
70+
true => Results.NoContent(),
71+
false => Results.Problem(statusCode: StatusCodes.Status404NotFound),
72+
};
73+
});
74+
}
75+
76+
return builder;
77+
}
78+
79+
public class TodoContext(DbContextOptions<TodoContext> options) : DbContext(options)
80+
{
81+
public DbSet<TodoItem> Items { get; set; } = default!;
82+
}
83+
84+
public class TodoRepository(TimeProvider timeProvider, TodoContext context)
85+
{
86+
public async Task<TodoItem> AddItemAsync(string text)
87+
{
88+
await this.EnsureDatabaseAsync();
89+
90+
var item = new TodoItem
91+
{
92+
CreatedAt = this.UtcNow(),
93+
Text = text
94+
};
95+
96+
context.Add(item);
97+
98+
await context.SaveChangesAsync();
99+
100+
return item;
101+
}
102+
103+
public async Task<bool?> CompleteItemAsync(Guid itemId)
104+
{
105+
var item = await this.GetItemAsync(itemId);
106+
107+
if (item is null)
108+
{
109+
return null;
110+
}
111+
112+
if (item.CompletedAt.HasValue)
113+
{
114+
return false;
115+
}
116+
117+
item.CompletedAt = this.UtcNow();
118+
119+
context.Items.Update(item);
120+
121+
await context.SaveChangesAsync();
122+
123+
return true;
124+
}
125+
126+
public async Task<bool> DeleteItemAsync(Guid itemId)
127+
{
128+
var item = await this.GetItemAsync(itemId);
129+
130+
if (item is null)
131+
{
132+
return false;
133+
}
134+
135+
context.Items.Remove(item);
136+
137+
await context.SaveChangesAsync();
138+
139+
return true;
140+
}
141+
142+
public async Task<TodoItem?> GetItemAsync(Guid itemId)
143+
{
144+
await this.EnsureDatabaseAsync();
145+
146+
return await context.Items.FindAsync([itemId]);
147+
}
148+
149+
public async Task<IList<TodoItem>> GetItemsAsync()
150+
{
151+
await this.EnsureDatabaseAsync();
152+
153+
return await context.Items
154+
.OrderBy(x => x.CompletedAt.HasValue)
155+
.ThenBy(x => x.CreatedAt)
156+
.ToListAsync();
157+
}
158+
159+
private async Task EnsureDatabaseAsync() => await context.Database.EnsureCreatedAsync();
160+
161+
private DateTime UtcNow() => timeProvider.GetUtcNow().UtcDateTime;
162+
}
163+
164+
public class CreateTodoItemModel
165+
{
166+
public string Text { get; set; } = string.Empty;
167+
}
168+
169+
public class TodoItem
170+
{
171+
public Guid Id { get; set; }
172+
173+
public string Text { get; set; } = default!;
174+
175+
public DateTime CreatedAt { get; set; }
176+
177+
public DateTime? CompletedAt { get; set; }
178+
}
179+
}

examples/net8.0/aspnetcore/aspnetcore.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@
1111

1212
<ItemGroup>
1313
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
14+
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.16" />
1415
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.20.1" />
1516
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
1617
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
17-
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
18+
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.4" />
1819
</ItemGroup>
1920

2021
<ItemGroup>

0 commit comments

Comments
 (0)