Skip to content

Commit 7d03641

Browse files
authored
Merge pull request #191 from clowder-framework/release/1.15.1
Release/1.15.1
2 parents 3c16b47 + 69f4760 commit 7d03641

File tree

6 files changed

+115
-100
lines changed

6 files changed

+115
-100
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/)
55
and this project adheres to [Semantic Versioning](http://semver.org/).
66

7+
## 1.15.1 - 2021-03-12
8+
9+
### Fixed
10+
- Several views were throwing errors trying to access a None value in `EventSinkService` when a user was not logged in.
11+
Replaced `get()` with `getOrElse()`.
12+
- Consolidated field names sent by the EventSinkService to maximize reuse.
13+
- Changed `EventSinkService` logging to debug to minimize chatter.
14+
- Don't automatically create eventsink queue and bind it to eventsink exchange. Let clients do that so that we don't
15+
have a queue for the eventsink filling up if there are no consumers.
16+
717
## 1.15.0 - 2021-03-03
818

919
### Added

app/services/EventSinkService.scala

Lines changed: 95 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,21 @@ import java.net.URI
66
import java.time.Instant
77
import play.api.{Logger, Play}
88
import play.api.Play.current
9-
import play.api.libs.json.{JsValue, Json}
9+
import play.api.libs.json.{JsObject, JsValue, Json}
1010

1111
object EventSinkService {
1212
val EXCHANGE_NAME_CONFIG_KEY = "eventsink.exchangename"
1313
val QUEUE_NAME_CONFIG_KEY = "eventsink.queuename"
1414

1515
val EXCHANGE_NAME_DEFAULT_VALUE = "clowder.metrics"
16-
val QUEUE_NAME_DEFAULT_VALUE = "event.sink"
17-
18-
// TODO: Make sure these match the real config key names
19-
val AMPLITUDE_CONFIG_KEY = "amplitude.apikey"
20-
val GA_CONFIG_KEY = "google.analytics"
21-
val INFLUX_AUTH_CONFIG_KEY = "influx.uri"
22-
val MONGO_AUTH_CONFIG_KEY = "mongo.uri"
16+
val QUEUE_NAME_DEFAULT_VALUE = ""
2317
}
2418

2519
class EventSinkService {
2620
val messageService: MessageService = DI.injector.getInstance(classOf[MessageService])
2721
val userService: UserService = DI.injector.getInstance(classOf[UserService])
2822
val appConfig: AppConfigurationService = DI.injector.getInstance(classOf[AppConfigurationService])
2923

30-
// UNUSED: Fetch directly from config on demand
31-
def getGoogleAnalytics(): String = Play.configuration.getString(EventSinkService.GA_CONFIG_KEY).getOrElse("")
32-
def getAmplitudeApiKey(): String = Play.configuration.getString(EventSinkService.AMPLITUDE_CONFIG_KEY).getOrElse("")
33-
def getMongoAuth(): String = Play.configuration.getString(EventSinkService.AMPLITUDE_CONFIG_KEY).getOrElse("")
34-
def getInfluxAuth(): String = Play.configuration.getString(EventSinkService.INFLUX_AUTH_CONFIG_KEY).getOrElse("")
35-
3624
/** Event Sink exchange name in RabbitMQ */
3725
val exchangeName = Play.configuration.getString(EventSinkService.EXCHANGE_NAME_CONFIG_KEY)
3826
.getOrElse(EventSinkService.EXCHANGE_NAME_DEFAULT_VALUE)
@@ -41,20 +29,22 @@ class EventSinkService {
4129
val queueName = Play.configuration.getString(EventSinkService.QUEUE_NAME_CONFIG_KEY)
4230
.getOrElse(EventSinkService.QUEUE_NAME_DEFAULT_VALUE)
4331

44-
def logEvent(category: String, metadata: JsValue) = {
45-
Logger.info("eventsink.exchangename=" + exchangeName)
46-
Logger.info("eventsink.queueName=" + queueName)
47-
48-
Logger.info("Submitting message to event sink exchange: " + Json.stringify(metadata))
49-
50-
//val message = EventSinkMessage(Instant.now().getEpochSecond, category, metadata)
51-
messageService.submit(exchangeName, queueName, metadata, "fanout")
32+
def logEvent(message: JsValue) = {
33+
// Inject timestamp before logging the event
34+
val event = message.as[JsObject] + ("created" -> Json.toJson(java.util.Date.from(Instant.now())))
35+
Logger.info("Submitting message to event sink exchange: " + Json.stringify(event))
36+
try {
37+
messageService.submit(exchangeName, queueName, event, "fanout")
38+
} catch {
39+
case e: Throwable => { Logger.error("Failed to submit event sink message", e) }
40+
}
5241
}
5342

5443
/** Log an event when user signs up */
5544
def logUserSignupEvent(user: User) = {
56-
Logger.info("New user signed up: " + user.id.stringify)
57-
logEvent("user_activity", Json.obj(
45+
Logger.debug("New user signed up: " + user.id.stringify)
46+
logEvent(Json.obj(
47+
"category" -> "user_activity",
5848
"type" -> "signup",
5949
"user_id" -> user.id,
6050
"user_name" -> user.fullName
@@ -63,8 +53,9 @@ class EventSinkService {
6353

6454
/** Log an event when user logs in */
6555
def logUserLoginEvent(user: User) = {
66-
Logger.info("User logged in: " + user.id.stringify)
67-
logEvent("user_activity", Json.obj(
56+
Logger.debug("User logged in: " + user.id.stringify)
57+
logEvent(Json.obj(
58+
"category" -> "user_activity",
6859
"type" -> "login",
6960
"user_id" -> user.id,
7061
"user_name" -> user.fullName
@@ -73,193 +64,203 @@ class EventSinkService {
7364

7465
/** Log an event when user views a dataset */
7566
def logDatasetViewEvent(dataset: Dataset, viewer: Option[User]) = {
76-
Logger.info("User viewed a dataset: " + dataset.id.stringify)
77-
logEvent("view_resource", Json.obj(
67+
Logger.debug("User viewed a dataset: " + dataset.id.stringify)
68+
logEvent(Json.obj(
69+
"category" -> "view_resource",
7870
"type" -> "dataset",
7971
"resource_id" -> dataset.id,
8072
"resource_name" -> dataset.name,
8173
"author_id" -> dataset.author.id,
8274
"author_name" -> dataset.author.fullName,
83-
"viewer_id" -> viewer.get.id,
84-
"viewer_name" -> viewer.get.getMiniUser.fullName
75+
"user_id" -> viewer.getOrElse(User.anonymous).id,
76+
"user_name" -> viewer.getOrElse(User.anonymous).getMiniUser.fullName
8577
))
8678
}
8779

8880
/** Log an event when user views a file */
8981
def logFileViewEvent(file: File, viewer: Option[User]) = {
90-
Logger.info("User viewed a file: " + file.id.stringify)
91-
logEvent("view_resource", Json.obj(
82+
Logger.debug("User viewed a file: " + file.id.stringify)
83+
logEvent(Json.obj(
84+
"category" -> "view_resource",
9285
"type" -> "file",
9386
"resource_id" -> file.id,
9487
"resource_name" -> file.filename,
9588
"author_id" -> file.author.id,
9689
"author_name" -> file.author.fullName,
97-
"viewer_id" -> viewer.get.id,
98-
"viewer_name" -> viewer.get.getMiniUser.fullName
90+
"user_id" -> viewer.getOrElse(User.anonymous).id,
91+
"user_name" -> viewer.getOrElse(User.anonymous).getMiniUser.fullName
9992
))
10093
}
10194

10295
/** Log an event when user views a collection */
10396
def logCollectionViewEvent(collection: Collection, viewer: Option[User]) = {
104-
Logger.info("User viewed a collection: " + collection.id.stringify)
105-
logEvent("view_resource", Json.obj(
97+
Logger.debug("User viewed a collection: " + collection.id.stringify)
98+
logEvent(Json.obj(
99+
"category" -> "view_resource",
106100
"type" -> "collection",
107101
"resource_id" -> collection.id,
108102
"resource_name" -> collection.name,
109103
"author_id" -> collection.author.id,
110104
"author_name" -> collection.author.fullName,
111-
"viewer_id" -> viewer.get.id,
112-
"viewer_name" -> viewer.get.getMiniUser.fullName
105+
"user_id" -> viewer.getOrElse(User.anonymous).id,
106+
"user_name" -> viewer.getOrElse(User.anonymous).getMiniUser.fullName
113107
))
114108
}
115109

116110
/** Log an event when user views a space */
117111
def logSpaceViewEvent(space: ProjectSpace, viewer: Option[User]) = {
118-
Logger.info("User viewed a space: " + space.id.stringify)
112+
Logger.debug("User viewed a space: " + space.id.stringify)
119113
(viewer, userService.get(space.creator)) match {
120114
case (Some(v), Some(author)) => {
121-
logEvent("view_resource", Json.obj(
115+
logEvent(Json.obj(
116+
"category" -> "view_resource",
122117
"type" -> "space",
123118
"resource_id" -> space.id,
124119
"resource_name" -> space.name,
125120
"author_id" -> space.creator.stringify,
126121
"author_name" -> author.fullName,
127-
"viewer_id" -> v.id,
128-
"viewer_name" -> v.getMiniUser.fullName
122+
"user_id" -> v.id,
123+
"user_name" -> v.getMiniUser.fullName
129124
))
130125
}
131126
case (None, Some(author)) => {
132-
logEvent("view_resource", Json.obj(
127+
logEvent(Json.obj(
128+
"category" -> "view_resource",
133129
"type" -> "space",
134130
"resource_id" -> space.id,
135131
"resource_name" -> space.name,
136132
"author_id" -> author.id,
137133
"author_name" -> author.fullName,
138-
"viewer_id" -> "",
139-
"viewer_name" -> "Anonymous"
134+
"user_id" -> User.anonymous.id,
135+
"user_name" -> User.anonymous.fullName
140136
))
141137
}
142138
case (Some(v), None) => {
143139
// TODO: Is this a real case? Is this needed?
144-
logEvent("view_resource", Json.obj(
140+
logEvent(Json.obj(
141+
"category" -> "view_resource",
145142
"type" -> "space",
146143
"resource_id" -> space.id,
147144
"resource_name" -> space.name,
148145
"author_id" -> space.creator.stringify,
149146
"author_name" -> "",
150-
"viewer_id" -> v.id,
151-
"viewer_name" -> v.getMiniUser.fullName
147+
"user_id" -> v.id,
148+
"user_name" -> v.getMiniUser.fullName
152149
))
153150
}
154151
case (None, None) => {
155152
// TODO: Is this a real case? Is this needed?
156-
logEvent("view_resource", Json.obj(
153+
logEvent(Json.obj(
154+
"category" -> "view_resource",
157155
"type" -> "space",
158156
"resource_id" -> space.id,
159157
"resource_name" -> space.name,
160158
"author_id" -> space.creator.stringify,
161159
"author_name" -> "",
162-
"viewer_id" -> "",
163-
"viewer_name" -> "Anonymous"
160+
"user_id" -> User.anonymous.id,
161+
"user_name" -> User.anonymous.fullName
164162
))
165163
}
166164
}
167165
}
168166

169167
def logSubmitFileToExtractorEvent(file: File, extractorName: String, submitter: Option[User]) = {
170-
logEvent("extraction", Json.obj(
168+
logEvent(Json.obj(
169+
"category" -> "extraction",
171170
"type" -> "file",
172171
"extractor_name" -> extractorName,
173172
"resource_id" -> file.id,
174173
"resource_name" -> file.filename,
175174
"author_id" -> file.author.id,
176175
"author_name" -> file.author.fullName,
177-
"submitter_id" -> submitter.get.id,
178-
"submitter_name" -> submitter.get.getMiniUser.fullName
176+
"user_id" -> submitter.getOrElse(User.anonymous).id,
177+
"user_name" -> submitter.getOrElse(User.anonymous).getMiniUser.fullName
179178
))
180179
}
181180

182181
def logSubmitDatasetToExtractorEvent(dataset: Dataset, extractorName: String, submitter: Option[User]) = {
183-
logEvent("extraction", Json.obj(
182+
logEvent(Json.obj(
183+
"category" -> "extraction",
184184
"type" -> "dataset",
185185
"extractor_name" -> extractorName,
186186
"resource_id" -> dataset.id,
187187
"resource_name" -> dataset.name,
188188
"author_id" -> dataset.author.id,
189189
"author_name" -> dataset.author.fullName,
190-
"submitter_id" -> submitter.get.id,
191-
"submitter_name" -> submitter.get.getMiniUser.fullName
190+
"user_id" -> submitter.getOrElse(User.anonymous).id,
191+
"user_name" -> submitter.getOrElse(User.anonymous).getMiniUser.fullName
192192
))
193193
}
194194

195195
def logSubmitSelectionToExtractorEvent(dataset: Dataset, extractorName: String, submitter: Option[User]) = {
196196
// TODO: Is this a real case? Is this needed?
197-
logEvent("extraction", Json.obj(
197+
logEvent(Json.obj(
198+
"category" -> "extraction",
198199
"type" -> "selection",
199200
"extractor_name" -> extractorName,
200201
"resource_id" -> dataset.id,
201202
"resource_name" -> dataset.name,
202203
"author_id" -> dataset.author.id,
203204
"author_name" -> dataset.author.fullName,
204-
"submitter_id" -> submitter.get.id,
205-
"submitter_name" -> submitter.get.getMiniUser.fullName
205+
"user_id" -> submitter.getOrElse(User.anonymous).id,
206+
"user_name" -> submitter.getOrElse(User.anonymous).getMiniUser.fullName
206207
))
207208
}
208209

209210
def logFileUploadEvent(file: File, dataset: Option[Dataset], uploader: Option[User]) = {
210211
dataset match {
211212
case Some(d) => {
212-
logEvent("upload", Json.obj(
213+
logEvent(Json.obj(
214+
"category" -> "upload",
213215
"dataset_id" -> d.id,
214216
"dataset_name" -> d.name,
215-
"dataset_author_name" -> d.author.fullName,
216-
"dataset_author_id" -> d.author.id,
217-
"uploader_id" -> uploader.get.id,
218-
"uploader_name" -> uploader.get.getMiniUser.fullName,
219-
"filename" -> file.filename,
220-
"length" -> file.length
217+
"author_name" -> d.author.fullName,
218+
"author_id" -> d.author.id,
219+
"user_id" -> uploader.getOrElse(User.anonymous).id,
220+
"user_name" -> uploader.getOrElse(User.anonymous).getMiniUser.fullName,
221+
"resource_name" -> file.filename,
222+
"size" -> file.length
221223
))
222224
}
223225
case None => {
224-
logEvent("upload", Json.obj(
225-
"uploader_id" -> uploader.get.id,
226-
"uploader_name" -> uploader.get.getMiniUser.fullName,
227-
"filename" -> file.filename,
228-
"length" -> file.length
226+
logEvent(Json.obj(
227+
"category" -> "upload",
228+
"user_id" -> uploader.getOrElse(User.anonymous).id,
229+
"user_name" -> uploader.getOrElse(User.anonymous).getMiniUser.fullName,
230+
"resource_name" -> file.filename,
231+
"size" -> file.length
229232
))
230233
}
231234
}
232235
}
233236

234-
def logFileDownloadEvent(file: File, /*dataset: Dataset,*/ downloader: Option[User]) = {
235-
logEvent("download", Json.obj(
236-
/*"dataset_id" -> dataset.id,
237-
"dataset_name" -> dataset.name,
238-
"dataset_author_name" -> dataset.author.fullName,
239-
"dataset_author_id" -> dataset.author.id,*/
237+
def logFileDownloadEvent(file: File, downloader: Option[User]) = {
238+
logEvent(Json.obj(
239+
"category" -> "download",
240240
"type" -> "file",
241-
"uploader_id" -> file.author.id,
242-
"uploader_name" -> file.author.fullName,
243-
"downloader_id" -> downloader.get.id,
244-
"downloader_name" -> downloader.get.getMiniUser.fullName,
245-
"filename" -> file.filename,
246-
"length" -> file.length
241+
"resource_id" -> file.id,
242+
"resource_name" -> file.filename,
243+
"author_id" -> file.author.id,
244+
"author_name" -> file.author.fullName,
245+
"user_id" -> downloader.getOrElse(User.anonymous).id,
246+
"user_name" -> downloader.getOrElse(User.anonymous).getMiniUser.fullName,
247+
"size" -> file.length
247248
))
248249
}
249250

250251
def logDatasetDownloadEvent(dataset: Dataset, downloader: Option[User]) = {
251-
logEvent("download", Json.obj(
252+
logEvent(Json.obj(
253+
"category" -> "download",
252254
"type" -> "dataset",
253-
"dataset_id" -> dataset.id,
254-
"dataset_name" -> dataset.name,
255-
"dataset_author_name" -> dataset.author.fullName,
256-
"dataset_author_id" -> dataset.author.id,
257-
"downloader_id" -> downloader.get.id,
258-
"downloader_name" -> downloader.get.getMiniUser.fullName,
259-
"files_length" -> dataset.files.length,
260-
"folder_length" -> dataset.folders.length
255+
"resource_id" -> dataset.id,
256+
"resource_name" -> dataset.name,
257+
"author_name" -> dataset.author.fullName,
258+
"author_id" -> dataset.author.id,
259+
"user_id" -> downloader.getOrElse(User.anonymous).id,
260+
"user_name" -> downloader.getOrElse(User.anonymous).getMiniUser.fullName,
261+
"size" -> (dataset.files.length + dataset.folders.length)
261262
))
262263
}
263264
}
264265

265-
//case class EventSinkMessage(created: Long, category: String, metadata: JsValue)
266+
//case class EventSinkMessage(created: Long, category: String, metadata: JsValue)

app/services/rabbitmq/RabbitMQMessageService.scala

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -230,11 +230,15 @@ class RabbitMQMessageService extends MessageService {
230230

231231
/** Submit a message to broker. */
232232
override def submit(exchange: String, routing_key: String, message: JsValue, exchange_type: String = "topic") = {
233-
// This probably isn't going to extract queue (use other submit() for that) so make a new broker
233+
connect()
234234
val tempChannel = connection.get.createChannel()
235235
tempChannel.exchangeDeclare(exchange, exchange_type, true)
236-
tempChannel.queueDeclare(routing_key, true, false, false, null)
237-
tempChannel.queueBind(routing_key, exchange, routing_key)
236+
237+
// If a routing_key (queue name) was provided, ensure that the queue exists
238+
if (routing_key != "") {
239+
tempChannel.queueDeclare(routing_key, true, false, false, null)
240+
tempChannel.queueBind(routing_key, exchange, routing_key)
241+
}
238242
tempChannel.basicPublish(exchange, routing_key, null, message.toString.getBytes)
239243
}
240244

doc/src/sphinx/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
author = 'Luigi Marini'
2323

2424
# The full version, including alpha/beta/rc tags
25-
release = '1.15.0'
25+
release = '1.15.1'
2626

2727

2828
# -- General configuration ---------------------------------------------------

0 commit comments

Comments
 (0)