Browse Source

Working on ModelServer

David Sehnal 6 years ago
parent
commit
e6c259a719

+ 4 - 1
src/mol-model/structure/structure/symmetry.ts

@@ -43,7 +43,10 @@ namespace StructureSymmetry {
         });
     }
 
-    // TODO: build symmetry mates within radius
+    export function builderSymmetryMates(structure: Structure, radius: number) {
+        // TODO: do it properly
+        return buildSymmetryRange(structure, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3));
+    }
 
     export function buildSymmetryRange(structure: Structure, ijkMin: Vec3, ijkMax: Vec3) {
         return Task.create('Build Assembly', async ctx => {

+ 42 - 0
src/mol-util/console-logger.ts

@@ -0,0 +1,42 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export namespace ConsoleLogger {
+    export function formatTime(t: number) {
+        if (isNaN(t)) return 'n/a';
+
+        let h = Math.floor(t / (60 * 60 * 1000)),
+            m = Math.floor(t / (60 * 1000) % 60),
+            s = Math.floor(t / 1000 % 60),
+            ms = Math.floor(t % 1000).toString();
+
+        while (ms.length < 3) ms = '0' + ms;
+
+        if (h > 0) return `${h}h${m}m${s}.${ms}s`;
+        if (m > 0) return `${m}m${s}.${ms}s`;
+        if (s > 0) return `${s}.${ms}s`;
+        return `${t.toFixed(0)}ms`;
+    }
+
+    export function log(tag: string, msg: string) {
+        console.log(`[${tag}] ${msg}`);
+    }
+
+    export function logId(guid: string, tag: string, msg: string) {
+        console.log(`[${guid}][${tag}] ${msg}`);
+    }
+
+    export function error(ctx: string, e: any) {
+        console.error(`[Error] (${ctx}) ${e}`);
+        if (e.stack) console.error(e.stack);
+    }
+
+
+    export function errorId(guid: string, e: any) {
+        console.error(`[${guid}][Error] ${e}`);
+        if (e.stack) console.error(e.stack);
+    }
+}

+ 63 - 0
src/servers/model/config.ts

@@ -0,0 +1,63 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+const config = {
+    /**
+     * Determine if and how long to cache entries after a request.
+     */
+    cacheParams: {
+        useCache: true,
+        maxApproximateSizeInBytes: 2 * 1014 * 1024 * 1024, // 2 GB
+        entryTimeoutInMs: 10 * 60 * 1000 // 10 minutes
+    },
+
+    /**
+     * Node (V8) sometimes exhibits GC related issues  that significantly slow down the execution
+     * (https://github.com/nodejs/node/issues/8670).
+     *
+     * Therefore an option is provided that automatically shuts down the server.
+     * For this to work, the server must be run using a deamon (i.e. forever.js on Linux
+     * or IISnode on Windows) so that the server is automatically restarted when the shutdown happens.
+     */
+    shutdownParams: {
+        // 0 for off, server will shut down after this amount of minutes.
+        timeoutMinutes: 24 * 60 /* a day */,
+
+        // modifies the shutdown timer by +/- timeoutVarianceMinutes (to avoid multiple instances shutting at the same time)
+        timeoutVarianceMinutes: 60
+    },
+
+    defaultPort: 1337,
+
+    /**
+     * Specify the prefix of the API, i.e.
+     * <host>/<apiPrefix>/<API queries>
+     */
+    appPrefix: '/ModelServer',
+
+    /**
+     * The maximum time the server dedicates to executing a query.
+     * Does not include the time it takes to read and export the data.
+     */
+    maxQueryTimeInMs: 5 * 1000,
+
+    /**
+     * Maps a request identifier to a filename.
+     * 
+     * @param source
+     *   Source of the data.
+     * @param id
+     *   Id provided in the request.
+     */
+    mapFile(source: string, id: string) {
+        switch (source.toLowerCase()) {
+            case 'pdb': return `e:/test/quick/${id}_updated.cif`;
+            default: return void 0;
+        }
+    }
+};
+
+export default config;

+ 0 - 0
src/servers/model/local.ts


+ 0 - 0
src/servers/model/server.ts


+ 172 - 0
src/servers/model/server/api.ts

@@ -0,0 +1,172 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Query, Queries, Structure, Element, StructureSymmetry } from 'mol-model/structure';
+
+export enum QueryParamType {
+    String,
+    Integer,
+    Float
+}
+
+export interface QueryParamInfo {
+    name: string,
+    type: QueryParamType,
+    description?: string,
+    required?: boolean,
+    defaultValue?: any,
+    exampleValue?: string,
+    validation?: (v: any) => void
+}
+
+export interface QueryDefinition {
+    niceName: string,
+    exampleId: string, // default is 1cbs
+    query: (params: any, originalStructure: Structure, transformedStructure: Structure) => Query.Provider,
+    description: string,
+    params: QueryParamInfo[],
+    structureTransform?: (params: any, s: Structure) => Promise<Structure>
+}
+
+const AtomSiteParameters = {
+    entity_id: <QueryParamInfo>{ name: 'entity_id', type: QueryParamType.String, description: 'Corresponds to the \'_entity.id\' or \'*.label_entity_id\' field, depending on the context.' },
+
+    label_asym_id: <QueryParamInfo>{ name: 'label_asym_id', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.label_asym_id\' field.' },
+    auth_asym_id: <QueryParamInfo>{ name: 'auth_asym_id', type: QueryParamType.String, exampleValue: 'A', description: 'Corresponds to the \'_atom_site.auth_asym_id\' field.' },
+
+    label_comp_id: <QueryParamInfo>{ name: 'label_comp_id', type: QueryParamType.String, description: 'Residue name. Corresponds to the \'_atom_site.label_comp_id\' field.' },
+    auth_comp_id: <QueryParamInfo>{ name: 'auth_comp_id', type: QueryParamType.String, exampleValue: 'REA', description: 'Author residue name. Corresponds to the \'_atom_site.auth_comp_id\' field.' },
+
+    pdbx_PDB_ins_code: <QueryParamInfo>{ name: 'pdbx_PDB_ins_code', type: QueryParamType.String, description: 'Corresponds to the \'_atom_site.pdbx_PDB_ins_code\' field.' },
+
+    label_seq_id: <QueryParamInfo>{ name: 'label_seq_id', type: QueryParamType.Integer, description: 'Residue seq. number. Corresponds to the \'_atom_site.label_seq_id\' field.' },
+    auth_seq_id: <QueryParamInfo>{ name: 'auth_seq_id', type: QueryParamType.Integer, exampleValue: '200', description: 'Author residue seq. number. Corresponds to the \'_atom_site.auth_seq_id\' field.' },
+};
+
+// function entityTest(params: any): Element.Predicate | undefined {
+//     if (typeof params.entity_id === 'undefined') return void 0;
+//     const p = Queries.props.entity.id, id = '' + params.entityId;
+//     return Element.property(l => p(l) === id);
+// }
+
+function entityTest1_555(params: any): Element.Predicate | undefined {
+    const oper = Queries.props.unit.operator_name;
+    if (typeof params.entity_id === 'undefined') return Element.property(l => oper(l) === '1_555');
+    const p = Queries.props.entity.id, id = '' + params.entityId;
+    return Element.property(l => p(l) === id && oper(l) === '1_555');
+}
+
+function chainTest(params: any): Element.Predicate | undefined {
+    if (typeof params.label_asym_id !== 'undefined') {
+        const p = Queries.props.chain.label_asym_id, id = '' + params.label_asym_id;
+        return Element.property(l => p(l) === id);
+    }
+    if (typeof params.auth_asym_id !== 'undefined') {
+        const p = Queries.props.chain.auth_asym_id, id = '' + params.auth_asym_id;
+        return Element.property(l => p(l) === id);
+    }
+    return void 0;
+}
+
+function residueTest(params: any): Element.Predicate | undefined {
+    if (typeof params.label_seq_id !== 'undefined') {
+        const p = Queries.props.residue.label_seq_id, id = +params.label_seq_id;
+        if (typeof params.pdbx_PDB_ins_code !== 'undefined') {
+            const p1 = Queries.props.residue.label_seq_id, id1 = params.pdbx_PDB_ins_code;
+            return Element.property(l => p(l) === id && p1(l) === id1);
+        }
+        return Element.property(l => p(l) === id);
+    }
+    if (typeof params.auth_seq_id !== 'undefined') {
+        const p = Queries.props.residue.auth_seq_id, id = +params.auth_seq_id;
+        if (typeof params.pdbx_PDB_ins_code !== 'undefined') {
+            const p1 = Queries.props.residue.label_seq_id, id1 = params.pdbx_PDB_ins_code;
+            return Element.property(l => p(l) === id && p1(l) === id1);
+        }
+        return Element.property(l => p(l) === id);
+    }
+    return void 0;
+}
+
+// function buildResiduesQuery(params: any): Query.Provider {
+//     return Queries.generators.atoms({ entityTest: entityTest(params), chainTest: chainTest(params), residueTest: residueTest(params) });
+// }
+
+const QueryMap: { [id: string]: Partial<QueryDefinition> } = {
+    'full': { niceName: 'Full Structure', query: () => Queries.generators.all, description: 'The full structure.' },
+    'residueInteraction': {
+        niceName: 'Residues Inside a Sphere',
+        description: 'Identifies all residues within the given radius from the source residue.',
+        query(p) {
+            const center = Queries.generators.atoms({ entityTest: entityTest1_555(p), chainTest: chainTest(p), residueTest: residueTest(p) });
+            return Queries.modifiers.includeSurroundings(center, { radius: p.radius, wholeResidues: true });
+        },
+        structureTransform(p, s) {
+            return StructureSymmetry.builderSymmetryMates(p, p. radius).run();
+        },
+        params: [
+            AtomSiteParameters.entity_id,
+            AtomSiteParameters.label_asym_id,
+            AtomSiteParameters.auth_asym_id,
+            AtomSiteParameters.label_comp_id,
+            AtomSiteParameters.auth_comp_id,
+            AtomSiteParameters.pdbx_PDB_ins_code,
+            AtomSiteParameters.label_seq_id,
+            AtomSiteParameters.auth_seq_id,
+            {
+                name: 'radius',
+                type: QueryParamType.Float,
+                defaultValue: 5,
+                exampleValue: '5',
+                description: 'Value in Angstroms.',
+                validation(v: any) {
+                    if (v < 1 || v > 10) {
+                        throw `Invalid radius for residue interaction query (must be a value between 1 and 10).`;
+                    }
+                }
+            },
+        ]
+    },
+}
+
+export function getQueryByName(name: string) {
+    return QueryMap[name];
+}
+
+export const QueryList = (function () {
+    const list: { name: string, definition: QueryDefinition }[] = [];
+    for (const k of Object.keys(QueryMap)) list.push({ name: k, definition: <QueryDefinition>QueryMap[k] });
+    list.sort(function (a, b) { return a.name < b.name ? -1 : a.name > b.name ? 1 : 0 });
+    return list;
+})();
+
+function _normalizeQueryParams(params: { [p: string]: string }, paramList: QueryParamInfo[]): { [p: string]: string | number | boolean } {
+    const ret: any = {};
+    for (const p of paramList) {
+        const key = p.name;
+
+        if (typeof params[key] === 'undefined' || (params[key] !== null && params[key]['length'] === 0)) {
+            if (p.required) {
+                throw `The parameter '${key}' is required.`;
+            }
+            ret[key] = p.defaultValue;
+        } else {
+            switch (p.type) {
+                case QueryParamType.String: ret[key] = params[key]; break;
+                case QueryParamType.Integer: ret[key] = parseInt(params[key]); break;
+                case QueryParamType.Float: ret[key] = parseFloat(params[key]); break;
+            }
+
+            if (p.validation) p.validation(ret[key]);
+        }
+    }
+
+    return ret;
+}
+
+export function normalizeQueryParams(query: QueryDefinition, params: any) {
+    return _normalizeQueryParams(params, query.params);
+}

+ 100 - 0
src/servers/model/server/cache.ts

@@ -0,0 +1,100 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { ConsoleLogger } from 'mol-util/console-logger'
+import { LinkedList } from 'mol-data/generic';
+import ServerConfig from '../config';
+
+interface CacheEntry<T> {
+    key: string,
+    approximateSize: number,
+    timeoutId: NodeJS.Timer | undefined,
+    item: T
+}
+
+type CacheNode<T> = LinkedList.Node<CacheEntry<T>>
+
+export interface CacheParams {
+    useCache: boolean,
+    maxApproximateSizeInBytes: number, // = 2 * 1014 * 1024 * 1024; // 2 GB
+    entryTimeoutInMs: number // = 10 * 60 * 1000; // 10 minutes
+}
+
+export class Cache<T> {
+    private entries = LinkedList<CacheEntry<T>>();
+    private entryMap = new Map<string, CacheNode<T>>();
+    private approximateSize = 0;
+
+    private clearTimeout(e: CacheNode<T>) {
+        if (typeof e.value.timeoutId !== 'undefined') {
+            clearTimeout(e.value.timeoutId);
+            e.value.timeoutId = void 0;
+        }
+    }
+
+    private dispose(e: CacheNode<T>) {
+        this.clearTimeout(e);
+
+        if (e.inList) {
+            this.entries.remove(e);
+            this.approximateSize -= e.value.approximateSize;
+        }
+        this.entryMap.delete(e.value.key);
+    }
+
+    private refresh(e: CacheNode<T>) {
+        this.clearTimeout(e);
+
+        e.value.timeoutId = setTimeout(() => this.expire(e), ServerConfig.cacheParams.entryTimeoutInMs);
+        this.entries.remove(e);
+        this.entries.addFirst(e.value);
+    }
+
+    private expire(e: CacheNode<T>, notify = true) {
+        if (notify) ConsoleLogger.log('Cache', `${e.value.key} expired.`);
+        this.dispose(e);
+    }
+
+    expireAll() {
+        for (let e = this.entries.first; e; e = e.next) this.expire(e, false);
+    }
+
+    add(item: T) {
+        const key = this.keyGetter(item);
+        const approximateSize = this.sizeGetter(item);
+
+        if (this.entryMap.has(key)) this.dispose(this.entryMap.get(key)!);
+
+        if (ServerConfig.cacheParams.maxApproximateSizeInBytes < this.approximateSize + approximateSize) {
+            if (this.entries.last) this.dispose(this.entries.last);
+        }
+
+        this.approximateSize += approximateSize;
+        const entry: CacheEntry<T> = { key, approximateSize, timeoutId: void 0, item };
+        const e = this.entries.addFirst(entry);
+        this.entryMap.set(key, e);
+        this.refresh(e);
+        ConsoleLogger.log('Cache', `${key} added.`);
+
+        return item;
+    }
+
+    has(key: string) {
+        return this.entryMap.has(key);
+    }
+
+
+    get(key: string) {
+        if (!this.entryMap.has(key)) return void 0;
+        let e = this.entryMap.get(key)!;
+        this.refresh(e);
+        ConsoleLogger.log('Cache', `${key} accessed.`);
+        return e.value.item;
+    }
+
+    constructor(private keyGetter: (i: T) => string, private sizeGetter: (i: T) => number) {
+    }
+}

+ 31 - 0
src/servers/model/server/query.ts

@@ -0,0 +1,31 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { UUID } from 'mol-util';
+
+export interface ResponseFormat {
+
+}
+
+export interface Query {
+    id: UUID,
+
+    sourceId: 'file' | string,
+    entryId: string,
+
+    kind: string,
+    params: any,
+
+    responseFormat: ResponseFormat
+}
+
+// export class QueryQueue {
+
+// }
+
+export function resolveQuery() {
+
+}

+ 36 - 0
src/servers/model/server/structure-wrapper.ts

@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Structure } from 'mol-model/structure';
+
+export enum StructureSourceType {
+    File,
+    Cache
+}
+
+export interface StructureInfo {
+    sourceType: StructureSourceType;
+    readTime: number;
+    parseTime: number;
+
+    sourceId: string,
+    entryId: string,
+    filename: string
+}
+
+export class StructureWrapper {
+    info: StructureInfo;
+
+    key: string;
+    approximateSize: number;
+    structure: Structure;
+}
+
+export function getStructure(filename: string): Promise<StructureWrapper>
+export function getStructure(sourceId: string, entryId: string): Promise<StructureWrapper>
+export function getStructure(sourceIdOrFilename: string, entryId?: string): Promise<StructureWrapper> {
+    return 0 as any;
+}

+ 7 - 0
src/servers/model/version.ts

@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export default '0.1.0';

+ 4 - 4
src/servers/volume/server.ts

@@ -12,12 +12,12 @@ import * as compression from 'compression'
 import init from './server/web-api'
 import VERSION from './server/version'
 import ServerConfig from './server-config'
-import * as Logger from './server/utils/logger'
+import { ConsoleLogger } from 'mol-util/console-logger'
 import { State } from './server/state'
 
 function setupShutdown() {
     if (ServerConfig.shutdownParams.timeoutVarianceMinutes > ServerConfig.shutdownParams.timeoutMinutes) {
-        Logger.logPlain('Server', 'Shutdown timeout variance is greater than the timer itself, ignoring.');
+        ConsoleLogger.log('Server', 'Shutdown timeout variance is greater than the timer itself, ignoring.');
     } else {
         let tVar = 0;
         if (ServerConfig.shutdownParams.timeoutVarianceMinutes > 0) {
@@ -26,7 +26,7 @@ function setupShutdown() {
         let tMs = (ServerConfig.shutdownParams.timeoutMinutes + tVar) * 60 * 1000;
 
         console.log(`----------------------------------------------------------------------------`);
-        console.log(`  The server will shut down in ${Logger.formatTime(tMs)} to prevent slow performance.`);
+        console.log(`  The server will shut down in ${ConsoleLogger.formatTime(tMs)} to prevent slow performance.`);
         console.log(`  Please make sure a daemon is running that will automatically restart it.`);
         console.log(`----------------------------------------------------------------------------`);
         console.log();
@@ -35,7 +35,7 @@ function setupShutdown() {
             if (State.pendingQueries > 0) {
                 State.shutdownOnZeroPending = true;
             } else {
-                Logger.logPlain('Server', `Shut down due to timeout.`);
+                ConsoleLogger.log('Server', `Shut down due to timeout.`);
                 process.exit(0);
             }
         }, tMs);

+ 5 - 5
src/servers/volume/server/api.ts

@@ -9,7 +9,7 @@
 import * as File from '../common/file'
 import execute from './query/execute'
 import * as Data from './query/data-model'
-import * as Logger from './utils/logger'
+import { ConsoleLogger } from 'mol-util/console-logger'
 import * as DataFormat from '../common/data-format'
 import ServerConfig from '../server-config'
 
@@ -27,10 +27,10 @@ export function getOutputFilename(source: string, id: string, { asBinary, box, d
 
 /** Reads the header and includes information about available detail levels */
 export async function getHeaderJson(filename: string | undefined, sourceId: string) {
-    Logger.logPlain('Header', sourceId);
+    ConsoleLogger.log('Header', sourceId);
     try {
         if (!filename || !File.exists(filename)) {
-            Logger.errorPlain(`Header ${sourceId}`, 'File not found.');
+            ConsoleLogger.error(`Header ${sourceId}`, 'File not found.');
             return void 0;
         }
         const header = { ...await readHeader(filename, sourceId) } as DataFormat.Header;
@@ -47,7 +47,7 @@ export async function getHeaderJson(filename: string | undefined, sourceId: stri
         (header as any).isAvailable = true;
         return JSON.stringify(header, null, 2);
     } catch (e) {
-        Logger.errorPlain(`Header ${sourceId}`, e);
+        ConsoleLogger.error(`Header ${sourceId}`, e);
         return void 0;
     }
 }
@@ -64,7 +64,7 @@ async function readHeader(filename: string | undefined, sourceId: string) {
         const header = await DataFormat.readHeader(file);
         return header.header;
     } catch (e) {
-        Logger.errorPlain(`Info ${sourceId}`, e);
+        ConsoleLogger.error(`Info ${sourceId}`, e);
         return void 0;
     } finally {
         File.close(file);

+ 4 - 4
src/servers/volume/server/query/execute.ts

@@ -11,7 +11,7 @@ import * as File from '../../common/file'
 import * as Data from './data-model'
 import * as Coords from '../algebra/coordinate'
 import * as Box from '../algebra/box'
-import * as Logger from '../utils/logger'
+import { ConsoleLogger } from 'mol-util/console-logger'
 import { State } from '../state'
 import ServerConfig from '../../server-config'
 
@@ -28,7 +28,7 @@ export default async function execute(params: Data.QueryParams, outputProvider:
 
     const guid = UUID.create() as any as string;
     params.detail = Math.min(Math.max(0, params.detail | 0), ServerConfig.limits.maxOutputSizeInVoxelCountByPrecisionLevel.length - 1);
-    Logger.log(guid, 'Info', `id=${params.sourceId},encoding=${params.asBinary ? 'binary' : 'text'},detail=${params.detail},${queryBoxToString(params.box)}`);
+    ConsoleLogger.logId(guid, 'Info', `id=${params.sourceId},encoding=${params.asBinary ? 'binary' : 'text'},detail=${params.detail},${queryBoxToString(params.box)}`);
 
     let sourceFile: number | undefined = void 0;
     try {
@@ -36,11 +36,11 @@ export default async function execute(params: Data.QueryParams, outputProvider:
         await _execute(sourceFile, params, guid, outputProvider);
         return true;
     } catch (e) {
-        Logger.error(guid, e);
+        ConsoleLogger.errorId(guid, e);
         return false;
     } finally {
         File.close(sourceFile);
-        Logger.log(guid, 'Time', `${Math.round(getTime() - start)}ms`);
+        ConsoleLogger.logId(guid, 'Time', `${Math.round(getTime() - start)}ms`);
         State.pendingQueries--;
     }
 }

+ 0 - 42
src/servers/volume/server/utils/logger.ts

@@ -1,42 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * Taken/adapted from DensityServer (https://github.com/dsehnal/DensityServer)
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-export function formatTime(t: number) {
-    if (isNaN(t)) return 'n/a';
-
-    let h = Math.floor(t / (60 * 60 * 1000)),
-        m = Math.floor(t / (60 * 1000) % 60),
-        s = Math.floor(t / 1000 % 60),
-        ms = Math.floor(t % 1000).toString();
-
-    while (ms.length < 3) ms = '0' + ms;
-
-    if (h > 0) return `${h}h${m}m${s}.${ms}s`;
-    if (m > 0) return `${m}m${s}.${ms}s`;
-    if (s > 0) return `${s}.${ms}s`;
-    return `${t.toFixed(0)}ms`;
-}
-
-export function logPlain(tag: string, msg: string) {
-    console.log(`[${tag}] ${msg}`);
-}
-
-export function log(guid: string, tag: string, msg: string) {
-    console.log(`[${guid}][${tag}] ${msg}`);
-}
-
-export function errorPlain(ctx: string, e: any) {
-    console.error(`[Error] (${ctx}) ${e}`);
-    if (e.stack) console.error(e.stack);
-}
-
-
-export function error(guid: string, e: any) {
-    console.error(`[${guid}][Error] ${e}`);
-    if (e.stack) console.error(e.stack);
-}

+ 4 - 4
src/servers/volume/server/web-api.ts

@@ -14,7 +14,7 @@ import * as Data from './query/data-model'
 import * as Coords from './algebra/coordinate'
 import Docs from './documentation'
 import ServerConfig from '../server-config'
-import * as Logger from './utils/logger'
+import { ConsoleLogger } from 'mol-util/console-logger'
 import { State } from './state'
 
 export default function init(app: express.Express) {
@@ -89,7 +89,7 @@ function validateSourndAndId(req: express.Request, res: express.Response) {
     if (!req.params.source || req.params.source.length > 32 || !req.params.id || req.params.source.id > 32) {
         res.writeHead(404);
         res.end();
-        Logger.errorPlain(`Query Box`, 'Invalid source and/or id');
+        ConsoleLogger.error(`Query Box`, 'Invalid source and/or id');
         return true;
     }
     return false;
@@ -117,7 +117,7 @@ async function getHeader(req: express.Request, res: express.Response) {
         headerWritten = true;
         res.write(header);
     } catch (e) {
-        Logger.errorPlain(`Header ${req.params.source}/${req.params.id}`, e);
+        ConsoleLogger.error(`Header ${req.params.source}/${req.params.id}`, e);
         if (!headerWritten) {
             res.writeHead(404);
         }
@@ -171,7 +171,7 @@ async function queryBox(req: express.Request, res: express.Response, params: Dat
             return;
         }
     } catch (e) {
-        Logger.errorPlain(`Query Box ${JSON.stringify(req.params || {})} | ${JSON.stringify(req.query || {})}`, e);
+        ConsoleLogger.error(`Query Box ${JSON.stringify(req.params || {})} | ${JSON.stringify(req.query || {})}`, e);
         response.do404();
     } finally {
         response.end();