Просмотр исходного кода

Improve API requests logging

The downloadable log now contains extra information:
- The window location (the URL relative path) from which the API request
was made
- The JS stack of the API request (the functions initiating the request)

It also contains some meta data: browser information, platform
information and UI version.

Polling requests are now ignored in the downloadable log and in the
developer’s tools log, to avoid logs pollution.
Sergiu Miclea 6 лет назад
Родитель
Сommit
6489318060

+ 2 - 7
src/components/pages/AboutModal/AboutModal.jsx

@@ -18,7 +18,6 @@ import React from 'react'
 import { observer } from 'mobx-react'
 import styled from 'styled-components'
 
-import apiCaller from '../../../utils/ApiCaller'
 import logger from '../../../utils/ApiLogger'
 
 import Modal from '../../molecules/Modal/Modal'
@@ -91,21 +90,17 @@ type Props = {
 }
 
 type State = {
-  version: string,
   licenceAddMode: boolean,
 }
 
 @observer
 class AboutModal extends React.Component<Props, State> {
   state = {
-    version: '-',
     licenceAddMode: false,
   }
 
   componentWillMount() {
-    apiCaller.get('/version').then(res => {
-      this.setState({ version: res.data.version })
-    })
+    licenceStore.loadVersion()
     licenceStore.loadLicenceInfo()
   }
 
@@ -130,7 +125,7 @@ class AboutModal extends React.Component<Props, State> {
                 <Logo />
                 <Text>
                   <TextLine>
-                    <span>Version {this.state.version}</span>
+                    <span>Version {licenceStore.version || '-'}</span>
                     <span>|</span>
                     <LinkMock onClick={() => { logger.download() }} >Download Log</LinkMock>
                   </TextLine>

+ 1 - 1
src/components/pages/AssessmentsPage/AssessmentsPage.jsx

@@ -186,7 +186,7 @@ class AssessmentsPage extends React.Component<Props, State> {
       connectionInfo.subscription_id,
       selectedResourceGroup.name,
       userStore.loggedUser ? userStore.loggedUser.project.id : '',
-      { backgroundLoading: true }
+      { backgroundLoading: true, skipLog: true }
     ).then(() => {
       this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })

+ 5 - 1
src/components/pages/EndpointsPage/EndpointsPage.jsx

@@ -198,7 +198,11 @@ class EndpointsPage extends React.Component<{ history: any }, State> {
       return
     }
 
-    Promise.all([endpointStore.getEndpoints({ showLoading }), migrationStore.getMigrations(), replicaStore.getReplicas()]).then(() => {
+    Promise.all([
+      endpointStore.getEndpoints({ showLoading, skipLog: true }),
+      migrationStore.getMigrations({ skipLog: true }),
+      replicaStore.getReplicas({ skipLog: true }),
+    ]).then(() => {
       this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }

+ 2 - 2
src/components/pages/MigrationDetailsPage/MigrationDetailsPage.jsx

@@ -86,7 +86,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
   }
 
   loadMigrationWithInstances(migrationId: string, cache: boolean) {
-    migrationStore.getMigration(migrationId, true).then(() => {
+    migrationStore.getMigration(migrationId, { showLoading: true }).then(() => {
       let details = migrationStore.migrationDetails
       if (!details) {
         return
@@ -181,7 +181,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     if (this.state.showEditModal || this.stopPolling) {
       return
     }
-    migrationStore.getMigration(this.props.match.params.id, false).then(() => {
+    migrationStore.getMigration(this.props.match.params.id, { showLoading: false, skipLog: true }).then(() => {
       setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }

+ 4 - 1
src/components/pages/MigrationsPage/MigrationsPage.jsx

@@ -172,7 +172,10 @@ class MigrationsPage extends React.Component<{ history: any }, State> {
       return
     }
 
-    Promise.all([migrationStore.getMigrations(), endpointStore.getEndpoints()]).then(() => {
+    Promise.all([
+      migrationStore.getMigrations({ skipLog: true }),
+      endpointStore.getEndpoints({ skipLog: true }),
+    ]).then(() => {
       this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }

+ 5 - 2
src/components/pages/ProjectsPage/ProjectsPage.jsx

@@ -76,7 +76,7 @@ class ProjectsPage extends React.Component<{ history: any }, State> {
   }
 
   handleReloadButtonClick() {
-    projectStore.getProjects(true)
+    projectStore.getProjects({ showLoading: true })
     projectStore.getRoleAssignments()
   }
 
@@ -91,7 +91,10 @@ class ProjectsPage extends React.Component<{ history: any }, State> {
       return
     }
 
-    Promise.all([projectStore.getProjects(showLoading), projectStore.getRoleAssignments()]).then(() => {
+    Promise.all([
+      projectStore.getProjects({ showLoading, skipLog: true }),
+      projectStore.getRoleAssignments({ skipLog: true }),
+    ]).then(() => {
       this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }

+ 3 - 3
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.jsx

@@ -128,7 +128,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   loadReplicaWithInstances(replicaId: string, cache: boolean) {
-    replicaStore.getReplica(replicaId).then(() => {
+    replicaStore.getReplica(replicaId, { showLoading: true }).then(() => {
       let details = replicaStore.replicaDetails
       if (!details) {
         return
@@ -317,10 +317,10 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     }
 
     if (!this.props.match.params.page) {
-      replicaStore.getReplica(this.props.match.params.id, showLoading)
+      replicaStore.getReplica(this.props.match.params.id, { showLoading, skipLog: true })
     }
 
-    replicaStore.getReplicaExecutions(this.props.match.params.id, showLoading).then(() => {
+    replicaStore.getReplicaExecutions(this.props.match.params.id, { showLoading, skipLog: true }).then(() => {
       setTimeout(() => { this.pollData(false) }, configLoader.config.requestPollTimeout)
     })
   }

+ 4 - 1
src/components/pages/ReplicasPage/ReplicasPage.jsx

@@ -217,7 +217,10 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
       return
     }
 
-    Promise.all([replicaStore.getReplicas(), endpointStore.getEndpoints()]).then(() => {
+    Promise.all([
+      replicaStore.getReplicas({ skipLog: true }),
+      endpointStore.getEndpoints({ skipLog: true }),
+    ]).then(() => {
       if (!this.schedulePolling) {
         this.pollSchedule()
       }

+ 2 - 2
src/components/pages/UsersPage/UsersPage.jsx

@@ -79,7 +79,7 @@ class UsersPage extends React.Component<{ history: any }, State> {
 
   handleReloadButtonClick() {
     projectStore.getProjects()
-    userStore.getAllUsers(true)
+    userStore.getAllUsers({ showLoading: true })
   }
 
   pollData(showLoading?: boolean) {
@@ -87,7 +87,7 @@ class UsersPage extends React.Component<{ history: any }, State> {
       return
     }
 
-    userStore.getAllUsers(showLoading).then(() => {
+    userStore.getAllUsers({ showLoading, skipLog: true }).then(() => {
       this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }

+ 2 - 1
src/sources/AzureSource.js

@@ -96,7 +96,7 @@ class AzureSource {
 
   static previousReqId: string
 
-  static getAssessments(subscriptionId: string, resourceGroupName: string): Promise<Assessment[]> {
+  static getAssessments(subscriptionId: string, resourceGroupName: string, skipLog?: ?boolean): Promise<Assessment[]> {
     let cancelId = subscriptionId + resourceGroupName
     if (this.previousReqId) {
       Api.cancelRequests(this.previousReqId)
@@ -107,6 +107,7 @@ class AzureSource {
     return Api.send({
       url: Util.buildUrl(projectsUrl({ resourceGroupName, subscriptionId })),
       cancelId,
+      skipLog,
     }).then(projectsResponse => {
       if (!Util.isResponseValid(projectsResponse)) {
         return []

+ 5 - 2
src/sources/EndpointSource.js

@@ -40,8 +40,11 @@ let getBarbicanPayload = data => {
 }
 
 class EdnpointSource {
-  static getEndpoints(): Promise<Endpoint[]> {
-    return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/endpoints`).then(response => {
+  static getEndpoints(skipLog?: boolean): Promise<Endpoint[]> {
+    return Api.send({
+      url: `${servicesUrl.coriolis}/${Api.projectId}/endpoints`,
+      skipLog,
+    }).then(response => {
       let connections = []
       if (response.data.endpoints.length) {
         response.data.endpoints.forEach(endpoint => {

+ 10 - 4
src/sources/MigrationSource.js

@@ -51,16 +51,22 @@ class MigrationSourceUtils {
 }
 
 class MigrationSource {
-  static getMigrations(): Promise<MainItem[]> {
-    return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/migrations/detail`).then(response => {
+  static getMigrations(skipLog?: boolean): Promise<MainItem[]> {
+    return Api.send({
+      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations/detail`,
+      skipLog,
+    }).then(response => {
       let migrations = response.data.migrations
       MigrationSourceUtils.sortMigrations(migrations)
       return migrations
     })
   }
 
-  static getMigration(migrationId: string): Promise<MainItem> {
-    return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/migrations/${migrationId}`).then(response => {
+  static getMigration(migrationId: string, skipLog?: boolean): Promise<MainItem> {
+    return Api.send({
+      url: `${servicesUrl.coriolis}/${Api.projectId}/migrations/${migrationId}`,
+      skipLog,
+    }).then(response => {
       let migration = response.data.migration
       sortTasks(migration.tasks, MigrationSourceUtils.sortTaskUpdates)
       return migration

+ 10 - 4
src/sources/ProjectSource.js

@@ -22,8 +22,11 @@ import type { Project, Role, RoleAssignment } from '../types/Project'
 import type { User } from '../types/User'
 
 class ProjectsSource {
-  static getProjects(): Promise<Project[]> {
-    return Api.get(servicesUrl.projects).then((response) => {
+  static getProjects(skipLog?: boolean): Promise<Project[]> {
+    return Api.send({
+      url: servicesUrl.projects,
+      skipLog,
+    }).then((response) => {
       if (response.data.projects) {
         let projects: Project[] = response.data.projects
         projects.sort((a, b) => a.name.localeCompare(b.name))
@@ -39,8 +42,11 @@ class ProjectsSource {
     })
   }
 
-  static getRoleAssignments(): Promise<RoleAssignment[]> {
-    return Api.get(`${coriolisUrl}identity/role_assignments?include_names`).then(response => {
+  static getRoleAssignments(skipLog?: boolean): Promise<RoleAssignment[]> {
+    return Api.send({
+      url: `${coriolisUrl}identity/role_assignments?include_names`,
+      skipLog,
+    }).then(response => {
       let assignments: RoleAssignment[] = response.data.role_assignments
       assignments.sort((a1, a2) => a1.role.name.localeCompare(a2.role.name))
       return assignments

+ 15 - 6
src/sources/ReplicaSource.js

@@ -119,8 +119,11 @@ class ReplicaSourceUtils {
 }
 
 class ReplicaSource {
-  static getReplicas(): Promise<MainItem[]> {
-    return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/replicas/detail`).then(response => {
+  static getReplicas(skipLog?: boolean): Promise<MainItem[]> {
+    return Api.send({
+      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/detail`,
+      skipLog,
+    }).then(response => {
       let replicas = response.data.replicas
       replicas = ReplicaSourceUtils.filterDeletedExecutionsInReplicas(replicas)
       ReplicaSourceUtils.sortReplicas(replicas)
@@ -128,8 +131,11 @@ class ReplicaSource {
     })
   }
 
-  static getReplicaExecutions(replicaId: string): Promise<Execution[]> {
-    return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/detail`).then((response) => {
+  static getReplicaExecutions(replicaId: string, skipLog?: boolean): Promise<Execution[]> {
+    return Api.send({
+      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/detail`,
+      skipLog,
+    }).then((response) => {
       let executions = response.data.executions
       ReplicaSourceUtils.sortExecutionsAndTasks(executions)
 
@@ -137,8 +143,11 @@ class ReplicaSource {
     })
   }
 
-  static getReplica(replicaId: string): Promise<MainItem> {
-    return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}`).then(response => {
+  static getReplica(replicaId: string, skipLog?: boolean): Promise<MainItem> {
+    return Api.send({
+      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}`,
+      skipLog,
+    }).then(response => {
       let replica = response.data.replica
       replica.executions = ReplicaSourceUtils.filterDeletedExecutions(replica.executions)
       ReplicaSourceUtils.sortExecutions(replica.executions)

+ 2 - 2
src/sources/UserSource.js

@@ -177,9 +177,9 @@ class UserSource {
     return Api.get(`${servicesUrl.users}/${userId}`).then(response => response.data.user)
   }
 
-  static getAllUsers(): Promise<User[]> {
+  static getAllUsers(skipLog?: boolean): Promise<User[]> {
     let users: User[] = []
-    return Api.get(`${servicesUrl.users}`)
+    return Api.send({ url: `${servicesUrl.users}`, skipLog })
       .then(response => {
         users = response.data.users
         return utils.waitFor(() => Boolean(configLoader.config))

+ 2 - 2
src/stores/AzureStore.js

@@ -181,7 +181,7 @@ class AzureStore {
     subscriptionId: string,
     resourceGroupName: string,
     projectId: string,
-    options?: { backgroundLoading: boolean },
+    options?: { backgroundLoading: boolean, skipLog?: boolean },
   ): Promise<void> {
     let cookieProjectId = cookie.get('projectId') || 'null'
     if (projectId !== cookieProjectId) {
@@ -191,7 +191,7 @@ class AzureStore {
     if (!options || !options.backgroundLoading) {
       this.loadingAssessments = true
     }
-    return AzureSource.getAssessments(subscriptionId, resourceGroupName).then((assessments: Assessment[]) => {
+    return AzureSource.getAssessments(subscriptionId, resourceGroupName, options && options.skipLog).then((assessments: Assessment[]) => {
       this.loadingAssessments = false
 
       cookieProjectId = cookie.get('projectId') || 'null'

+ 2 - 2
src/stores/EndpointStore.js

@@ -43,12 +43,12 @@ class EndpointStore {
   @observable storageLoading: boolean = false
   @observable storageConfigDefault: string = ''
 
-  @action getEndpoints(options?: { showLoading: boolean }) {
+  @action getEndpoints(options?: { showLoading?: boolean, skipLog?: boolean }) {
     if (options && options.showLoading) {
       this.loading = true
     }
 
-    return EndpointSource.getEndpoints().then(endpoints => {
+    return EndpointSource.getEndpoints(options && options.skipLog).then(endpoints => {
       this.endpoints = endpoints
       this.loading = false
     }).catch(() => {

+ 15 - 0
src/stores/LicenceStore.js

@@ -16,13 +16,28 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import { observable, action, runInAction } from 'mobx'
 
+import apiCaller from '../utils/ApiCaller'
 import licenceSource from '../sources/LincenceSource'
+
 import type { Licence } from '../types/Licence'
 
 class LicenceStore {
   @observable loadingLicenceInfo: boolean = false
   @observable licenceInfo: ?Licence = null
   @observable addingLicence: boolean = false
+  @observable version: ?string = null
+
+  async loadVersion(): Promise<string> {
+    if (this.version) {
+      return this.version
+    }
+
+    let response = await apiCaller.get('/version')
+    runInAction(() => {
+      this.version = response.data.version
+    })
+    return this.version || ''
+  }
 
   @action async loadLicenceInfo() {
     this.loadingLicenceInfo = true

+ 5 - 5
src/stores/MigrationStore.js

@@ -30,12 +30,12 @@ class MigrationStore {
 
   migrationsLoaded: boolean = false
 
-  @action getMigrations(options?: { showLoading: boolean }) {
+  @action getMigrations(options?: { showLoading?: boolean, skipLog?: boolean }) {
     if ((options && options.showLoading) || !this.migrationsLoaded) {
       this.loading = true
     }
 
-    return MigrationSource.getMigrations().then(migrations => {
+    return MigrationSource.getMigrations(options && options.skipLog).then(migrations => {
       this.migrations = migrations.map(migration => {
         let oldMigration = this.migrations.find(r => r.id === migration.id)
         if (oldMigration) {
@@ -67,10 +67,10 @@ class MigrationStore {
     })
   }
 
-  @action getMigration(migrationId: string, showLoading: boolean) {
-    this.detailsLoading = showLoading
+  @action getMigration(migrationId: string, options?: { showLoading?: boolean, skipLog?: boolean }) {
+    this.detailsLoading = Boolean(options && options.showLoading)
 
-    return MigrationSource.getMigration(migrationId).then(migration => {
+    return MigrationSource.getMigration(migrationId, options && options.skipLog).then(migration => {
       this.detailsLoading = false
       this.migrationDetails = migration
     }).catch(() => {

+ 5 - 5
src/stores/ProjectStore.js

@@ -31,9 +31,9 @@ class ProjectStore {
   @observable usersLoading: boolean = false
   @observable updating: boolean = false
 
-  @action getProjects(showLoading?: boolean): Promise<void> {
-    if (showLoading) this.loading = true
-    return ProjectSource.getProjects().then((projects: Project[]) => {
+  @action getProjects(options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
+    if (options && options.showLoading) this.loading = true
+    return ProjectSource.getProjects(options && options.skipLog).then((projects: Project[]) => {
       this.loading = false
       this.projects = projects
     }).catch(() => {
@@ -41,8 +41,8 @@ class ProjectStore {
     })
   }
 
-  @action getRoleAssignments(): Promise<void> {
-    return ProjectSource.getRoleAssignments().then((assignments: RoleAssignment[]) => {
+  @action getRoleAssignments(options?: { skipLog?: boolean }): Promise<void> {
+    return ProjectSource.getRoleAssignments(options && options.skipLog).then((assignments: RoleAssignment[]) => {
       this.roleAssignments = assignments
     })
   }

+ 8 - 8
src/stores/ReplicaStore.js

@@ -49,14 +49,14 @@ class ReplicaStore {
 
   replicasLoaded: boolean = false
 
-  @action getReplicas(options?: { showLoading: boolean }): Promise<void> {
+  @action getReplicas(options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
     this.backgroundLoading = true
 
     if ((options && options.showLoading) || !this.replicasLoaded) {
       this.loading = true
     }
 
-    return ReplicaSource.getReplicas().then(replicas => {
+    return ReplicaSource.getReplicas(options && options.skipLog).then(replicas => {
       this.replicas = replicas
       this.loading = false
       this.backgroundLoading = false
@@ -67,10 +67,10 @@ class ReplicaStore {
     })
   }
 
-  @action getReplicaExecutions(replicaId: string, showLoading: boolean = false): Promise<void> {
-    if (showLoading) this.executionsLoading = true
+  @action getReplicaExecutions(replicaId: string, options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
+    if (options && options.showLoading) this.executionsLoading = true
 
-    return ReplicaSource.getReplicaExecutions(replicaId).then(executions => {
+    return ReplicaSource.getReplicaExecutions(replicaId, options && options.skipLog).then(executions => {
       let replica = this.replicas.find(replica => replica.id === replicaId)
 
       if (replica) {
@@ -88,10 +88,10 @@ class ReplicaStore {
     }).catch(() => { this.executionsLoading = false })
   }
 
-  @action getReplica(replicaId: string, showLoading: boolean = true): Promise<void> {
-    this.detailsLoading = showLoading
+  @action getReplica(replicaId: string, options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
+    this.detailsLoading = Boolean(options && options.showLoading)
 
-    return ReplicaSource.getReplica(replicaId).then(replica => {
+    return ReplicaSource.getReplica(replicaId, options && options.skipLog).then(replica => {
       this.detailsLoading = false
       this.replicaDetails = replica
     }).catch(() => {

+ 3 - 3
src/stores/UserStore.js

@@ -144,10 +144,10 @@ class UserStore {
     })
   }
 
-  @action getAllUsers(showLoading?: boolean): Promise<void> {
-    if (showLoading) this.allUsersLoading = true
+  @action getAllUsers(options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
+    if (options && options.showLoading) this.allUsersLoading = true
 
-    return UserSource.getAllUsers().then(users => {
+    return UserSource.getAllUsers(options && options.skipLog).then(users => {
       this.users = users
       this.allUsersLoading = false
     }).catch(() => {

+ 1 - 1
src/utils/ApiCaller.js

@@ -34,7 +34,7 @@ type RequestOptions = {
   data?: any,
   responseType?: 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream',
   quietError?: boolean,
-  skipLog?: boolean,
+  skipLog?: ?boolean,
 }
 
 let cancelables: Cancelable[] = []

+ 47 - 12
src/utils/ApiLogger.js

@@ -14,6 +14,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 // @flow
 
+import licenceStore from '../stores/LicenceStore'
+
 type LogType = 'REQUEST' | 'RESPONSE'
 
 type LogOptions = {
@@ -23,36 +25,61 @@ type LogOptions = {
   description?: string,
   requestStatus?: number | 'canceled',
   requestError?: any,
+  windowPath?: string,
+  stack?: string,
 }
 
-type Log = LogOptions & {
+type RequestLog = LogOptions & {
   date: Date,
 }
-const MAX_LOGS = 3000
+
+type Log = {
+  requests: RequestLog[],
+  userAgent: string,
+  platform: string,
+  version: string,
+}
+
+const MAX_LOGS = 1000
+
+const validateLog = (logs: RequestLog[]): boolean => {
+  if (logs.length && !logs[0].windowPath) {
+    return false
+  }
+  return true
+}
+
 class Storage {
   static NAME = 'apiLog'
+  static EMPTY = '[]'
 
   static getLogRaw(): string {
-    return localStorage.getItem(this.NAME) || '[]'
-  }
-
-  static getLog(): Log[] {
-    let logs: Log[] = JSON.parse(localStorage.getItem(this.NAME) || '[]')
-    return logs
+    return localStorage.getItem(this.NAME) || this.EMPTY
   }
 
   static saveLog(options: LogOptions) {
-    let logs: Log[] = JSON.parse(localStorage.getItem(this.NAME) || '[]')
-    let newLog: Log = {
+    let logs: RequestLog[] = JSON.parse(localStorage.getItem(this.NAME) || this.EMPTY)
+    if (!validateLog(logs)) {
+      localStorage.setItem(this.NAME, this.EMPTY)
+      logs = JSON.parse(this.EMPTY)
+    }
+
+    let newRequest: RequestLog = {
       date: new Date(),
+      windowPath: window.location.href.replace(`${window.location.protocol}//${window.location.host}`, ''),
       ...options,
     }
 
+    if (options.type === 'REQUEST') {
+      let err = new Error()
+      newRequest.stack = err.stack
+    }
+
     if (logs.length > MAX_LOGS) {
       logs.splice(0, logs.length - MAX_LOGS)
     }
 
-    logs.push(newLog)
+    logs.push(newRequest)
     localStorage.setItem(this.NAME, JSON.stringify(logs))
   }
 }
@@ -77,7 +104,15 @@ class ApiLogger {
   }
 
   download() {
-    let href: string = `data:text/json;charset=utf-8,${encodeURIComponent(Storage.getLogRaw())}`
+    let requests: RequestLog[] = JSON.parse(Storage.getLogRaw())
+    let log: Log = {
+      requests,
+      userAgent: window.navigator.userAgent,
+      platform: window.navigator.platform,
+      version: licenceStore.version || '-',
+    }
+
+    let href: string = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(log))}`
     let downloadAnchorNode = document.createElement('a')
     downloadAnchorNode.setAttribute('href', href)
     downloadAnchorNode.setAttribute('download', 'coriolis-log.json')