mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 03:21:44 +03:00
Add remoteLog function for gathering gui logs (#6582)
Co-authored-by: Michał W. Urbańczyk <mwu-tow@gazeta.pl> Co-authored-by: Nikita Pekin <nikita@frecency.com>
This commit is contained in:
parent
af03938305
commit
1d77f7cd8e
@ -18,17 +18,15 @@ mod js {
|
||||
|
||||
#[wasm_bindgen(inline_js = "
|
||||
export function remote_log(msg, value) {
|
||||
try {
|
||||
window.ensoglApp.remoteLog(msg,value)
|
||||
} catch (error) {
|
||||
console.error(\"Error while logging message. \" + error );
|
||||
}
|
||||
window.ensoglApp.remoteLog(msg, value).catch((error) => {
|
||||
console.error(`Error while logging message. ${error}`)
|
||||
})
|
||||
}
|
||||
|
||||
export function remote_log_value(msg, field_name, value) {
|
||||
const data = {}
|
||||
data[field_name] = value
|
||||
remote_log(msg,data)
|
||||
remote_log(msg, data)
|
||||
}
|
||||
")]
|
||||
extern "C" {
|
||||
|
@ -3,11 +3,6 @@
|
||||
"authentication": {
|
||||
"value": true,
|
||||
"description": "Determines whether user authentication is enabled. This option is always true when executed in the cloud."
|
||||
},
|
||||
"dataCollection": {
|
||||
"value": true,
|
||||
"description": "Determines whether anonymous usage data is to be collected.",
|
||||
"primary": false
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
|
@ -10,6 +10,7 @@ import * as dashboard from 'enso-authentication'
|
||||
import * as detect from 'enso-common/src/detect'
|
||||
|
||||
import * as app from '../../../../../target/ensogl-pack/linked-dist'
|
||||
import * as remoteLog from './remoteLog'
|
||||
import GLOBAL_CONFIG from '../../../../gui/config.yaml' assert { type: 'yaml' }
|
||||
|
||||
const logger = app.log.logger
|
||||
@ -159,7 +160,7 @@ class Main implements AppRunner {
|
||||
|
||||
/** Run an app instance with the specified configuration.
|
||||
* This includes the scene to run and the WebSocket endpoints to the backend. */
|
||||
async runApp(inputConfig?: StringConfig | null) {
|
||||
async runApp(inputConfig?: StringConfig | null, accessToken?: string) {
|
||||
this.stopApp()
|
||||
|
||||
/** FIXME: https://github.com/enso-org/enso/issues/6475
|
||||
@ -174,7 +175,7 @@ class Main implements AppRunner {
|
||||
inputConfig
|
||||
)
|
||||
|
||||
this.app = new app.App({
|
||||
const newApp = new app.App({
|
||||
config,
|
||||
configOptions: contentConfig.OPTIONS,
|
||||
packageInfo: {
|
||||
@ -183,12 +184,27 @@ class Main implements AppRunner {
|
||||
},
|
||||
})
|
||||
|
||||
// We override the remote logger stub with the "real" one. Eventually the runner should not be aware of the
|
||||
// remote logger at all, and it should be integrated with our logging infrastructure.
|
||||
const remoteLogger = accessToken != null ? new remoteLog.RemoteLogger(accessToken) : null
|
||||
newApp.remoteLog = async (message: string, metadata: unknown) => {
|
||||
if (newApp.config.options.dataCollection.value && remoteLogger) {
|
||||
await remoteLogger.remoteLog(message, metadata)
|
||||
} else {
|
||||
const logMessage = [
|
||||
'Not sending log to remote server. Data collection is disabled.',
|
||||
`Message: "${message}"`,
|
||||
`Metadata: ${JSON.stringify(metadata)}`,
|
||||
].join(' ')
|
||||
|
||||
logger.log(logMessage)
|
||||
}
|
||||
}
|
||||
this.app = newApp
|
||||
|
||||
if (!this.app.initialized) {
|
||||
console.error('Failed to initialize the application.')
|
||||
} else {
|
||||
if (contentConfig.OPTIONS.options.dataCollection.value) {
|
||||
// TODO: Add remote-logging here.
|
||||
}
|
||||
if (!(await checkMinSupportedVersion(contentConfig.OPTIONS))) {
|
||||
displayDeprecatedVersionDialog()
|
||||
} else {
|
||||
@ -270,7 +286,7 @@ class Main implements AppRunner {
|
||||
isAuthenticationDisabled: !config.shouldUseAuthentication,
|
||||
shouldShowDashboard: config.shouldUseNewDashboard,
|
||||
initialProjectName: config.initialProjectName,
|
||||
onAuthenticated: () => {
|
||||
onAuthenticated: (accessToken?: string) => {
|
||||
if (config.isInAuthenticationFlow) {
|
||||
const initialUrl = localStorage.getItem(INITIAL_URL_KEY)
|
||||
if (initialUrl != null) {
|
||||
@ -287,7 +303,7 @@ class Main implements AppRunner {
|
||||
ideElement.style.display = ''
|
||||
}
|
||||
if (this.app == null) {
|
||||
void this.runApp(config.inputConfig)
|
||||
void this.runApp(config.inputConfig, accessToken)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
75
app/ide-desktop/lib/content/src/remoteLog.ts
Normal file
75
app/ide-desktop/lib/content/src/remoteLog.ts
Normal file
@ -0,0 +1,75 @@
|
||||
/** @file Defines the {@link RemoteLogger} class and {@link remoteLog} function for sending logs to a remote server.
|
||||
* {@link RemoteLogger} provides a convenient way to manage remote logging with access token authorization. */
|
||||
|
||||
import * as app from '../../../../../target/ensogl-pack/linked-dist'
|
||||
import * as authConfig from '../../dashboard/src/authentication/src/config'
|
||||
|
||||
const logger = app.log.logger
|
||||
|
||||
// =================
|
||||
// === Constants ===
|
||||
// =================
|
||||
|
||||
/** URL address where remote logs should be sent. */
|
||||
const REMOTE_LOG_URL = new URL(`${authConfig.ACTIVE_CONFIG.apiUrl}/logs`)
|
||||
|
||||
// ====================
|
||||
// === RemoteLogger ===
|
||||
// ====================
|
||||
|
||||
// === Class ===
|
||||
|
||||
/** Helper class facilitating sending logs to a remote. */
|
||||
export class RemoteLogger {
|
||||
/** Initialize a new instance.
|
||||
* @param accessToken - JWT token used to authenticate within the cloud. */
|
||||
constructor(public accessToken: string) {
|
||||
this.accessToken = accessToken
|
||||
}
|
||||
|
||||
/** Sends a log message to a remote.
|
||||
* @param message - The log message to send.
|
||||
* @param metadata - Additional metadata to send along with the log.
|
||||
* @returns Promise which resolves when the log message has been sent. */
|
||||
async remoteLog(message: string, metadata: unknown): Promise<void> {
|
||||
await remoteLog(this.accessToken, message, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
// === Underlying logic ===
|
||||
|
||||
/** Sends a log message to a remote server using the provided access token.
|
||||
*
|
||||
* @param accessToken - The access token for authentication.
|
||||
* @param message - The message to be logged on the server.
|
||||
* @param metadata - Additional metadata to include in the log.
|
||||
* @throws Will throw an error if the response from the server is not okay (response status is not 200).
|
||||
* @returns Returns a promise that resolves when the log message is successfully sent. */
|
||||
export async function remoteLog(
|
||||
accessToken: string,
|
||||
message: string,
|
||||
metadata: unknown
|
||||
): Promise<void> {
|
||||
try {
|
||||
const headers: HeadersInit = new Headers()
|
||||
headers.set('Content-Type', 'application/json')
|
||||
headers.set('Authorization', `Bearer ${accessToken}`)
|
||||
const response = await fetch(REMOTE_LOG_URL, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify({ message, metadata }),
|
||||
})
|
||||
if (!response.ok) {
|
||||
const errorMessage = `Error while sending log to a remote: Status ${response.status}.`
|
||||
try {
|
||||
const text = await response.text()
|
||||
throw new Error(`${errorMessage} Response: ${text}.`)
|
||||
} catch (error) {
|
||||
throw new Error(`${errorMessage} Failed to read response: ${String(error)}.`)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error)
|
||||
throw error
|
||||
}
|
||||
}
|
@ -166,7 +166,7 @@ export interface AuthProviderProps {
|
||||
supportsLocalBackend: boolean
|
||||
authService: authServiceModule.AuthService
|
||||
/** Callback to execute once the user has authenticated successfully. */
|
||||
onAuthenticated: () => void
|
||||
onAuthenticated: (accessToken?: string) => void
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
@ -290,7 +290,7 @@ export function AuthProvider(props: AuthProviderProps) {
|
||||
|
||||
/** Execute the callback that should inform the Electron app that the user has logged in.
|
||||
* This is done to transition the app from the authentication/dashboard view to the IDE. */
|
||||
onAuthenticated()
|
||||
onAuthenticated(accessToken)
|
||||
}
|
||||
|
||||
setUserSession(newUserSession)
|
||||
|
@ -62,6 +62,17 @@ const BASE_AMPLIFY_CONFIG = {
|
||||
|
||||
/** Collection of configuration details for Amplify user pools, sorted by deployment environment. */
|
||||
const AMPLIFY_CONFIGS = {
|
||||
/** Configuration for @indiv0's Cognito user pool. */
|
||||
npekin: {
|
||||
userPoolId: newtype.asNewtype<auth.UserPoolId>('eu-west-1_AXX1gMvpx'),
|
||||
userPoolWebClientId: newtype.asNewtype<auth.UserPoolWebClientId>(
|
||||
'1rpnb2n1ijn6o5529a7ob017o'
|
||||
),
|
||||
domain: newtype.asNewtype<auth.OAuthDomain>(
|
||||
'npekin-enso-domain.auth.eu-west-1.amazoncognito.com'
|
||||
),
|
||||
...BASE_AMPLIFY_CONFIG,
|
||||
} satisfies Partial<auth.AmplifyConfig>,
|
||||
/** Configuration for @pbuchu's Cognito user pool. */
|
||||
pbuchu: {
|
||||
userPoolId: newtype.asNewtype<auth.UserPoolId>('eu-west-1_jSF1RbgPK'),
|
||||
|
@ -26,11 +26,16 @@ const CLOUD_REDIRECTS = {
|
||||
/** All possible API URLs, sorted by environment. */
|
||||
const API_URLS = {
|
||||
pbuchu: newtype.asNewtype<ApiUrl>('https://xw0g8j3tsb.execute-api.eu-west-1.amazonaws.com'),
|
||||
npekin: newtype.asNewtype<ApiUrl>('https://s02ejyepk1.execute-api.eu-west-1.amazonaws.com'),
|
||||
production: newtype.asNewtype<ApiUrl>('https://7aqkn3tnbc.execute-api.eu-west-1.amazonaws.com'),
|
||||
}
|
||||
|
||||
/** All possible configuration options, sorted by environment. */
|
||||
const CONFIGS = {
|
||||
npekin: {
|
||||
cloudRedirect: CLOUD_REDIRECTS.development,
|
||||
apiUrl: API_URLS.npekin,
|
||||
} satisfies Config,
|
||||
pbuchu: {
|
||||
cloudRedirect: CLOUD_REDIRECTS.development,
|
||||
apiUrl: API_URLS.pbuchu,
|
||||
@ -61,7 +66,7 @@ export interface Config {
|
||||
|
||||
/** Possible values for the environment/user we're running for and whose infrastructure we're
|
||||
* testing against. */
|
||||
export type Environment = 'pbuchu' | 'production'
|
||||
export type Environment = 'npekin' | 'pbuchu' | 'production'
|
||||
|
||||
// ===========
|
||||
// === API ===
|
||||
|
@ -1,6 +1,7 @@
|
||||
/** @file Container that launches the IDE. */
|
||||
import * as React from 'react'
|
||||
|
||||
import * as auth from '../../authentication/providers/auth'
|
||||
import * as backendModule from '../backend'
|
||||
import * as backendProvider from '../../providers/backend'
|
||||
|
||||
@ -26,10 +27,11 @@ export interface IdeProps {
|
||||
appRunner: AppRunner
|
||||
}
|
||||
|
||||
/** The ontainer that launches the IDE. */
|
||||
/** The container that launches the IDE. */
|
||||
function Ide(props: IdeProps) {
|
||||
const { project, appRunner } = props
|
||||
const { backend } = backendProvider.useBackend()
|
||||
const { accessToken } = auth.useNonPartialUserSession()
|
||||
|
||||
React.useEffect(() => {
|
||||
void (async () => {
|
||||
@ -78,20 +80,25 @@ function Ide(props: IdeProps) {
|
||||
: {
|
||||
projectManagerUrl: GLOBAL_CONFIG.projectManagerEndpoint,
|
||||
}
|
||||
await appRunner.runApp({
|
||||
loader: {
|
||||
assetsUrl: `${assetsRoot}dynamic-assets`,
|
||||
wasmUrl: `${assetsRoot}pkg-opt.wasm`,
|
||||
jsUrl: `${assetsRoot}pkg${JS_EXTENSION[backend.type]}`,
|
||||
await appRunner.runApp(
|
||||
{
|
||||
loader: {
|
||||
assetsUrl: `${assetsRoot}dynamic-assets`,
|
||||
wasmUrl: `${assetsRoot}pkg-opt.wasm`,
|
||||
jsUrl: `${assetsRoot}pkg${JS_EXTENSION[backend.type]}`,
|
||||
},
|
||||
engine: {
|
||||
...engineConfig,
|
||||
preferredVersion: engineVersion,
|
||||
},
|
||||
startup: {
|
||||
project: project.packageName,
|
||||
},
|
||||
},
|
||||
engine: {
|
||||
...engineConfig,
|
||||
preferredVersion: engineVersion,
|
||||
},
|
||||
startup: {
|
||||
project: project.packageName,
|
||||
},
|
||||
})
|
||||
// Here we actually need explicit undefined.
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
accessToken ?? undefined
|
||||
)
|
||||
}
|
||||
if (backend.type === backendModule.BackendType.local) {
|
||||
await runNewProject()
|
||||
|
2
app/ide-desktop/lib/types/types.d.ts
vendored
2
app/ide-desktop/lib/types/types.d.ts
vendored
@ -13,5 +13,5 @@ interface StringConfig {
|
||||
* open a new IDE instance. */
|
||||
interface AppRunner {
|
||||
stopApp: () => void
|
||||
runApp: (config?: StringConfig) => Promise<void>
|
||||
runApp: (config?: StringConfig, accessToken?: string) => Promise<void>
|
||||
}
|
||||
|
@ -3,6 +3,10 @@
|
||||
"debug": {
|
||||
"value": false,
|
||||
"description": "Controls the debug mode for the application. In this mode, all logs are printed to the console, and EnsoGL extensions are loaded. Otherwise, logs are hidden unless explicitly shown with 'showLogs'."
|
||||
},
|
||||
"dataCollection": {
|
||||
"value": true,
|
||||
"description": "Determines whether anonymous usage data is to be collected."
|
||||
}
|
||||
},
|
||||
"groups": {
|
||||
|
@ -238,12 +238,11 @@ export class App {
|
||||
}
|
||||
|
||||
/** Log the message on the remote server. */
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
remoteLog(message: string, data: any) {
|
||||
// FIXME [PB]: https://github.com/enso-org/cloud-v2/issues/359
|
||||
// Implement remote logging. This should be done after cloud integration.
|
||||
// Function interface is left intentionally for readability.
|
||||
// Remove typescript error suppression after resolving fixme.
|
||||
// This method is assumed to be overriden by the App's owner. Eventually it should be removed from the runner
|
||||
// altogether, as it is not its responsibility.
|
||||
// eslint-disable-next-line @typescript-eslint/require-await, @typescript-eslint/no-unused-vars
|
||||
async remoteLog(message: string, data: any) {
|
||||
console.warn('Remote logging is not set up.')
|
||||
}
|
||||
|
||||
/** Initialize the browser. Set the background color, print user-facing warnings, etc. */
|
||||
@ -281,8 +280,9 @@ export class App {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets application stop to true and calls drop method which removes all rust memory references
|
||||
* and calls all destructors. */
|
||||
* and calls all destructors. */
|
||||
stop() {
|
||||
this.stopped = true
|
||||
this.wasm?.drop()
|
||||
|
Loading…
Reference in New Issue
Block a user